Commit Diff


commit - 610939e0b14ba1d0c5727058de651ce1afa0e10d
commit + 99beb4a5385f52e7b353501b0ee08f0e6855d8f2
blob - c57bddd73ec75cfe62db167973aa379b968ae268
blob + 89d9655a02f2cd1a90920cd514f0428a57739453
--- jackson.rs
+++ jackson.rs
@@ -38,7 +38,7 @@ pub struct Error {
 }
 
 impl Error {
-    const fn new(msg: &'static str, pos: usize) -> Self {
+    pub const fn new(msg: &'static str, pos: usize) -> Self {
         Self { msg, pos }
     }
 
@@ -498,6 +498,211 @@ impl<'a> Parser<'a> {
     }
 }
 
+/// Trait implemented by types that can be written to a [`Value`].
+pub trait ToJson {
+    fn to_json(&self) -> Value;
+}
+
+/// Trait implemented by types that can be read from a [`Value`].
+pub trait FromJson: Sized {
+    fn from_json(v: &Value) -> Result<Self, Error>;
+
+    /// Produce a value when the corresponding field is absent from the
+    /// parent object.  The default rejects; `Option<T>` overrides to
+    /// return `None`.
+    fn missing_field() -> Result<Self, Error> {
+        Err(Error::new("missing field", 0))
+    }
+}
+
+impl ToJson for bool {
+    fn to_json(&self) -> Value {
+        Value::Bool(*self)
+    }
+}
+
+impl FromJson for bool {
+    fn from_json(v: &Value) -> Result<Self, Error> {
+        v.as_bool().ok_or_else(|| Error::new("expected bool", 0))
+    }
+}
+
+impl ToJson for String {
+    fn to_json(&self) -> Value {
+        Value::String(self.clone())
+    }
+}
+
+impl FromJson for String {
+    fn from_json(v: &Value) -> Result<Self, Error> {
+        v.as_str()
+            .map(str::to_string)
+            .ok_or_else(|| Error::new("expected string", 0))
+    }
+}
+
+impl ToJson for f64 {
+    fn to_json(&self) -> Value {
+        Value::Number(*self)
+    }
+}
+
+impl FromJson for f64 {
+    fn from_json(v: &Value) -> Result<Self, Error> {
+        v.as_number()
+            .ok_or_else(|| Error::new("expected number", 0))
+    }
+}
+
+impl ToJson for f32 {
+    fn to_json(&self) -> Value {
+        Value::Number(*self as f64)
+    }
+}
+
+impl FromJson for f32 {
+    fn from_json(v: &Value) -> Result<Self, Error> {
+        v.as_number()
+            .map(|n| n as f32)
+            .ok_or_else(|| Error::new("expected number", 0))
+    }
+}
+
+macro_rules! jackson_int_impls {
+    ($($t:ty),* $(,)?) => { $(
+        impl ToJson for $t {
+            fn to_json(&self) -> Value {
+                Value::Number(*self as f64)
+            }
+        }
+
+        impl FromJson for $t {
+            fn from_json(v: &Value) -> Result<Self, Error> {
+                let n = v
+                    .as_number()
+                    .ok_or_else(|| Error::new("expected number", 0))?;
+                if n.is_finite()
+                    && n.fract() == 0.0
+                    && n >= <$t>::MIN as f64
+                    && n <= <$t>::MAX as f64
+                {
+                    Ok(n as $t)
+                } else {
+                    Err(Error::new("integer out of range", 0))
+                }
+            }
+        }
+    )* };
+}
+
+jackson_int_impls!(i8, i16, i32, i64, u8, u16, u32, u64);
+
+impl<T: ToJson> ToJson for Option<T> {
+    fn to_json(&self) -> Value {
+        match self {
+            Some(v) => v.to_json(),
+            None => Value::Null,
+        }
+    }
+}
+
+impl<T: FromJson> FromJson for Option<T> {
+    fn from_json(v: &Value) -> Result<Self, Error> {
+        match v {
+            Value::Null => Ok(None),
+            _ => T::from_json(v).map(Some),
+        }
+    }
+
+    fn missing_field() -> Result<Self, Error> {
+        Ok(None)
+    }
+}
+
+impl<T: ToJson> ToJson for Vec<T> {
+    fn to_json(&self) -> Value {
+        Value::Array(self.iter().map(T::to_json).collect())
+    }
+}
+
+impl<T: FromJson> FromJson for Vec<T> {
+    fn from_json(v: &Value) -> Result<Self, Error> {
+        let a = v
+            .as_array()
+            .ok_or_else(|| Error::new("expected array", 0))?;
+        a.iter().map(T::from_json).collect()
+    }
+}
+
+/// Define a struct together with [`ToJson`] and [`FromJson`] impls.
+#[macro_export]
+macro_rules! json_struct {
+    (
+        $(#[$meta:meta])*
+        $vis:vis struct $name:ident {
+            $(
+                $(#[$fmeta:meta])*
+                $fvis:vis $field:ident : $ty:ty
+            ),* $(,)?
+        }
+    ) => {
+        $(#[$meta])*
+        $vis struct $name {
+            $(
+                $(#[$fmeta])*
+                $fvis $field: $ty,
+            )*
+        }
+
+        impl $crate::ToJson for $name {
+            fn to_json(&self) -> $crate::Value {
+                $crate::Value::Object(vec![
+                    $(
+                        (
+                            stringify!($field).to_string(),
+                            <$ty as $crate::ToJson>::to_json(&self.$field),
+                        ),
+                    )*
+                ])
+            }
+        }
+
+        impl $crate::FromJson for $name {
+            fn from_json(
+                v: &$crate::Value,
+            ) -> ::std::result::Result<Self, $crate::Error> {
+                let obj = v.as_object().ok_or_else(|| {
+                    $crate::Error::new("expected object", 0)
+                })?;
+                $( let mut $field: Option<&$crate::Value> = None; )*
+                for (k, val) in obj {
+                    match k.as_str() {
+                        $( stringify!($field) => $field = Some(val), )*
+                        _ => {}
+                    }
+                }
+                Ok(Self {
+                    $(
+                        $field: match $field {
+                            Some(val) => {
+                                <$ty as $crate::FromJson>::from_json(val)?
+                            }
+                            None => <$ty as $crate::FromJson>::missing_field()
+                                .map_err(|_| $crate::Error::new(
+                                    concat!(
+                                        "missing field: ",
+                                        stringify!($field),
+                                    ),
+                                    0,
+                                ))?,
+                        },
+                    )*
+                })
+            }
+        }
+    };
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -819,4 +1024,148 @@ mod tests {
         let a = Value::Array(vec![Value::Number(f64::NAN)]);
         assert!(a.stringify().is_err());
     }
+
+    json_struct! {
+        struct SimpleUser {
+            name: String,
+            age: i64,
+            active: bool,
+        }
+    }
+
+    #[test]
+    fn derive_simple_round_trip() {
+        let u = SimpleUser {
+            name: "ada".into(),
+            age: 36,
+            active: true,
+        };
+        let s = u.to_json().stringify().unwrap();
+        let v: Value = s.parse().unwrap();
+        let back = SimpleUser::from_json(&v).unwrap();
+        assert_eq!(back.name, "ada");
+        assert_eq!(back.age, 36);
+        assert!(back.active);
+    }
+
+    json_struct! {
+        struct OptionalFields {
+            required: String,
+            maybe: Option<i64>,
+        }
+    }
+
+    #[test]
+    fn derive_option_none_serialises_as_null() {
+        let u = OptionalFields {
+            required: "x".into(),
+            maybe: None,
+        };
+        let s = u.to_json().stringify().unwrap();
+        assert_eq!(s, r#"{"required":"x","maybe":null}"#);
+        let back = OptionalFields::from_json(&s.parse().unwrap()).unwrap();
+        assert!(back.maybe.is_none());
+    }
+
+    #[test]
+    fn derive_absent_option_is_none() {
+        let v: Value = r#"{"required":"x"}"#.parse().unwrap();
+        let back = OptionalFields::from_json(&v).unwrap();
+        assert_eq!(back.required, "x");
+        assert!(back.maybe.is_none());
+    }
+
+    #[test]
+    fn derive_option_some_round_trips() {
+        let u = OptionalFields {
+            required: "x".into(),
+            maybe: Some(42),
+        };
+        let s = u.to_json().stringify().unwrap();
+        let back = OptionalFields::from_json(&s.parse().unwrap()).unwrap();
+        assert_eq!(back.maybe, Some(42));
+    }
+
+    json_struct! {
+        struct Container {
+            tags: Vec<String>,
+            counts: Vec<i32>,
+        }
+    }
+
+    #[test]
+    fn derive_vec_round_trip() {
+        let c = Container {
+            tags: vec!["a".into(), "b".into()],
+            counts: vec![1, 2, 3],
+        };
+        let s = c.to_json().stringify().unwrap();
+        let back = Container::from_json(&s.parse().unwrap()).unwrap();
+        assert_eq!(back.tags, vec!["a".to_string(), "b".to_string()]);
+        assert_eq!(back.counts, vec![1, 2, 3]);
+    }
+
+    json_struct! {
+        struct Inner {
+            value: i32,
+        }
+    }
+
+    json_struct! {
+        struct Outer {
+            name: String,
+            inner: Inner,
+        }
+    }
+
+    #[test]
+    fn derive_nested_struct() {
+        let o = Outer {
+            name: "n".into(),
+            inner: Inner { value: 7 },
+        };
+        let s = o.to_json().stringify().unwrap();
+        assert_eq!(s, r#"{"name":"n","inner":{"value":7}}"#);
+        let back = Outer::from_json(&s.parse().unwrap()).unwrap();
+        assert_eq!(back.name, "n");
+        assert_eq!(back.inner.value, 7);
+    }
+
+    #[test]
+    fn derive_missing_field_errors() {
+        let v: Value = r#"{"name":"ada","active":true}"#.parse().unwrap();
+        let e = SimpleUser::from_json(&v).err().unwrap();
+        assert_eq!(e.message(), "missing field: age");
+    }
+
+    #[test]
+    fn derive_wrong_type_errors() {
+        let v: Value =
+            r#"{"name":"ada","age":"old","active":true}"#.parse().unwrap();
+        assert!(SimpleUser::from_json(&v).is_err());
+    }
+
+    #[test]
+    fn derive_expects_object() {
+        let v = Value::Array(Vec::new());
+        assert!(SimpleUser::from_json(&v).is_err());
+    }
+
+    #[test]
+    fn derive_integer_out_of_range_errors() {
+        assert!(i32::from_json(&Value::Number(1e20)).is_err());
+    }
+
+    #[test]
+    fn derive_non_integer_errors() {
+        assert!(i64::from_json(&Value::Number(1.5)).is_err());
+    }
+
+    #[test]
+    fn derive_duplicate_keys_last_wins() {
+        let v: Value =
+            r#"{"name":"a","age":1,"active":true,"age":2}"#.parse().unwrap();
+        let back = SimpleUser::from_json(&v).unwrap();
+        assert_eq!(back.age, 2);
+    }
 }