commit - 610939e0b14ba1d0c5727058de651ce1afa0e10d
commit + 99beb4a5385f52e7b353501b0ee08f0e6855d8f2
blob - c57bddd73ec75cfe62db167973aa379b968ae268
blob + 89d9655a02f2cd1a90920cd514f0428a57739453
--- jackson.rs
+++ jackson.rs
}
impl Error {
- const fn new(msg: &'static str, pos: usize) -> Self {
+ pub const fn new(msg: &'static str, pos: usize) -> Self {
Self { msg, pos }
}
}
}
+/// 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::*;
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);
+ }
}