Add IntoJson trait to convert a rusty value into json
parent
b0f5df9ecb
commit
e3f41d7e0d
@ -0,0 +1,5 @@
|
||||
[[language]]
|
||||
name = "rust"
|
||||
|
||||
[language.config]
|
||||
cargo = { features = "all" }
|
@ -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;
|
Loading…
Reference in New Issue