Add IntoJson trait to convert a rusty value into json

main
trivernis 2 years ago
parent b0f5df9ecb
commit e3f41d7e0d
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -0,0 +1,5 @@
[[language]]
name = "rust"
[language.config]
cargo = { features = "all" }

@ -3,13 +3,16 @@ members = [".", "derive"]
[package]
name = "rusty-value"
version = "0.4.2"
version = "0.5.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/Trivernis/rusty-value"
description = "Create a generic inspectable value from any rust type"
authors = ["trivernis <trivernis@proton.me>"]
[dependencies]
serde_json = { version = "1.0.85", default-features = false, optional = true, features = ["std"]}
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.rusty-value-derive]
@ -20,6 +23,7 @@ optional = true
[features]
default = []
derive = ["rusty-value-derive"]
json = ["serde_json"]
[dev-dependencies.rusty-value-derive]
path = "./derive"

@ -12,8 +12,7 @@ The trait `RustyValue` allows one to create a `rusty_value::Value` for any
type that implements it. This trait can be derived if the `derive` **feature** is enabled.
```rust
use rusty_value::{RustyValue, Value};
use rusty_value::*;
#[derive(RustyValue)]
struct MyStruct {

@ -204,7 +204,8 @@ fn add_rusty_bound(generics: &Generics) -> WhereClause {
fn get_rusty_value_crate() -> proc_macro2::TokenStream {
use proc_macro_crate::{crate_name, FoundCrate};
match crate_name("rusty_value") {
Ok(FoundCrate::Itself) | Err(_) => quote!(rusty_value),
Ok(FoundCrate::Itself) => quote!(rusty_value),
Err(_) => quote!(crate),
Ok(FoundCrate::Name(name)) => quote!(#name),
}
}

@ -0,0 +1,307 @@
use serde_json::{Number, Value};
use std::string::ToString;
use crate::{Enum, HashableValue, Primitive, RustyValue, Struct};
/// Options for how to represent certain rust types
/// as JSON
#[derive(Clone, Debug, RustyValue, Default)]
pub struct IntoJsonOptions {
pub enum_repr: EnumRepr,
}
/// Controls how enums should be represented
/// This works similarly to serde except that internal tagging isn't supported
/// except that internal tagging isn't supported
#[derive(Clone, Debug, RustyValue)]
pub enum EnumRepr {
Untagged,
ExternallyTagged,
AdjacentlyTagged {
type_field: String,
value_field: String,
},
}
impl Default for EnumRepr {
fn default() -> Self {
Self::ExternallyTagged
}
}
/// Trait to convert a value into a json value
pub trait IntoJson {
/// Converts the value into a json value with default options
fn into_json(self) -> Value;
/// Converts the value into a json value with the given options
fn into_json_with_options(self, options: &IntoJsonOptions) -> Value;
}
trait RustyIntoJson {
/// Converts the value into a json value with default options
fn into_json(self) -> Value;
/// Converts the value into a json value with the given options
fn into_json_with_options(self, options: &IntoJsonOptions) -> Value;
}
impl RustyIntoJson for crate::Value {
#[inline]
fn into_json(self) -> Value {
self.into_json_with_options(&IntoJsonOptions::default())
}
fn into_json_with_options(self, opt: &IntoJsonOptions) -> Value {
match self {
crate::Value::Primitive(p) => p.into_json_with_options(opt),
crate::Value::Struct(s) => s.into_json_with_options(opt),
crate::Value::Enum(e) => e.into_json_with_options(opt),
crate::Value::Map(m) => Value::Object(
m.into_iter()
.map(|(k, v)| (hashable_to_string(k), v.into_json_with_options(opt)))
.collect(),
),
crate::Value::List(l) => Value::Array(
l.into_iter()
.map(|v| v.into_json_with_options(opt))
.collect(),
),
crate::Value::None => Value::Null,
}
}
}
impl IntoJson for Primitive {
fn into_json(self) -> Value {
match self {
Primitive::Integer(i) => match i {
crate::Integer::USize(n) => Value::Number(n.into()),
crate::Integer::ISize(n) => Value::Number(n.into()),
crate::Integer::U8(n) => Value::Number(n.into()),
crate::Integer::I8(n) => Value::Number(n.into()),
crate::Integer::U16(n) => Value::Number(n.into()),
crate::Integer::I16(n) => Value::Number(n.into()),
crate::Integer::U32(n) => Value::Number(n.into()),
crate::Integer::I32(n) => Value::Number(n.into()),
crate::Integer::U64(n) => Value::Number(n.into()),
crate::Integer::I64(n) => Value::Number(n.into()),
crate::Integer::U128(n) => Value::Array(vec![
((n >> 64) as u64).into(),
((n & 0xFFFFFFFFFFFFFFFF) as u64).into(),
]),
crate::Integer::I128(n) => Value::Array(vec![
((n >> 64) as i64).into(),
((n & 0xFFFFFFFFFFFFFFFF) as u64).into(),
]),
},
Primitive::Float(f) => match f {
crate::Float::F32(f) => Number::from_f64(f as f64)
.map(Value::Number)
.unwrap_or(Value::Null),
crate::Float::F64(f) => Number::from_f64(f)
.map(Value::Number)
.unwrap_or(Value::Null),
},
Primitive::String(s) => Value::String(s),
Primitive::OsString(o) => Value::String(o.to_string_lossy().into_owned()),
Primitive::Char(c) => Value::String(c.to_string()),
Primitive::Bool(b) => Value::Bool(b),
}
}
#[inline]
fn into_json_with_options(self, _options: &IntoJsonOptions) -> Value {
self.into_json()
}
}
impl IntoJson for Enum {
#[inline]
fn into_json(self) -> Value {
self.into_json_with_options(&IntoJsonOptions::default())
}
fn into_json_with_options(self, opt: &IntoJsonOptions) -> Value {
let value = match self.fields {
crate::Fields::Named(n) => Value::Object(
n.into_iter()
.map(|(k, v)| (k, v.into_json_with_options(opt)))
.collect(),
),
crate::Fields::Unnamed(mut u) => {
if u.len() == 1 {
u.remove(0).into_json_with_options(opt)
} else {
Value::Array(
u.into_iter()
.map(|v| v.into_json_with_options(opt))
.collect(),
)
}
}
crate::Fields::Unit => Value::String(self.variant.clone()),
};
match &opt.enum_repr {
EnumRepr::Untagged => value,
EnumRepr::ExternallyTagged => {
Value::Object([(self.variant, value)].into_iter().collect())
}
EnumRepr::AdjacentlyTagged {
type_field,
value_field,
} => Value::Object(
[
(type_field.to_owned(), Value::String(self.variant)),
(value_field.to_owned(), value),
]
.into_iter()
.collect(),
),
}
}
}
impl IntoJson for Struct {
#[inline]
fn into_json(self) -> Value {
self.into_json_with_options(&IntoJsonOptions::default())
}
fn into_json_with_options(self, opt: &IntoJsonOptions) -> Value {
match self.fields {
crate::Fields::Named(n) => Value::Object(
n.into_iter()
.map(|(k, v)| (k, v.into_json_with_options(opt)))
.collect(),
),
crate::Fields::Unnamed(mut u) => {
if u.len() == 1 {
u.remove(0).into_json_with_options(opt)
} else {
Value::Array(
u.into_iter()
.map(|v| v.into_json_with_options(opt))
.collect(),
)
}
}
crate::Fields::Unit => Value::String(self.name),
}
}
}
impl<R: RustyValue> IntoJson for R {
#[inline]
fn into_json(self) -> Value {
self.into_json_with_options(&IntoJsonOptions::default())
}
#[inline]
fn into_json_with_options(self, opt: &IntoJsonOptions) -> Value {
self.into_rusty_value().into_json_with_options(opt)
}
}
fn hashable_to_string(hashable: HashableValue) -> String {
match hashable {
HashableValue::Primitive(p) => p.to_string(),
HashableValue::List(l) => l
.into_iter()
.map(hashable_to_string)
.collect::<Vec<_>>()
.join(","),
HashableValue::None => String::new(),
}
}
#[cfg(test)]
mod test {
#![allow(unused)]
use serde_json::json;
use crate as rusty_value;
use crate::into_json::IntoJsonOptions;
use crate::RustyValue;
use super::IntoJson;
#[test]
fn it_serializes_primitives() {
assert_eq!(u8::MAX.into_json(), json!(u8::MAX));
assert_eq!(i8::MIN.into_json(), json!(i8::MIN));
assert_eq!(u16::MAX.into_json(), json!(u16::MAX));
assert_eq!(i16::MIN.into_json(), json!(i16::MIN));
assert_eq!(u32::MAX.into_json(), json!(u32::MAX));
assert_eq!(i32::MIN.into_json(), json!(i32::MIN));
assert_eq!(u64::MAX.into_json(), json!(u64::MAX));
assert_eq!(i64::MIN.into_json(), json!(i64::MIN));
assert_eq!(u128::MAX.into_json(), json!([u64::MAX, u64::MAX]));
assert_eq!(i128::MIN.into_json(), json!([i64::MIN, 0]));
}
#[derive(Default, RustyValue)]
struct TestStruct {
foo: String,
bar: u8,
}
#[test]
fn it_serializes_structs() {
let val = TestStruct::default();
let value = val.into_json();
assert!(value.is_object());
}
#[derive(RustyValue)]
enum TestEnum {
Foo,
Bar(String),
}
#[test]
fn it_serializes_unit_enums_untagged() {
let val = TestEnum::Foo;
let value = val.into_json_with_options(&IntoJsonOptions {
enum_repr: rusty_value::into_json::EnumRepr::Untagged,
});
assert!(value.is_string());
assert_eq!(value.as_str(), Some("Foo"))
}
#[test]
fn it_serializes_struct_enums_adjacently_tagged() {
let val = TestEnum::Bar(String::new());
let value = val.into_json_with_options(&IntoJsonOptions {
enum_repr: rusty_value::into_json::EnumRepr::AdjacentlyTagged {
type_field: "type".into(),
value_field: "value".into(),
},
});
println!("{}", value.to_string());
assert!(value.is_object());
assert_eq!(value.get("type").unwrap().as_str(), Some("Bar"));
assert!(value.get("value").unwrap().is_string());
}
#[test]
fn it_serializes_struct_enums_untagged() {
let val = TestEnum::Bar(String::new());
let value = val.into_json_with_options(&IntoJsonOptions {
enum_repr: rusty_value::into_json::EnumRepr::Untagged,
});
assert!(value.is_string());
assert_eq!(value.as_str(), Some(""));
}
#[test]
fn it_serializes_struct_enums_externally_tagged() {
let val = TestEnum::Bar(String::new());
let value = val.into_json_with_options(&IntoJsonOptions {
enum_repr: rusty_value::into_json::EnumRepr::ExternallyTagged,
});
assert!(value.is_object());
assert!(value.get("Bar").unwrap().is_string());
}
}

@ -0,0 +1,4 @@
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
/// Implements converting the [crate::Value] into a [serde_json::Value].
pub mod into_json;

@ -1,7 +1,9 @@
#![doc=include_str!("../README.md")]
pub(crate) mod formats;
pub(crate) mod value;
pub(crate) mod value_trait;
pub use formats::*;
pub use value::*;
pub use value_trait::*;

@ -1,3 +1,4 @@
use rusty_value::*;
use rusty_value::{Fields, RustyValue, Value};
#[allow(dead_code)]

@ -1,5 +1,6 @@
use std::path::PathBuf;
use rusty_value::*;
use rusty_value::{Fields, RustyValue, Value};
#[derive(RustyValue)]

Loading…
Cancel
Save