Search code examples
rustserdeserde-json

How to rename `start` and `end` range values with serde?


I have JSON objects with the following format:

{
  "name": "foo",
  "value": 1234,
  "upper_bound": 5000,
  "lower_bound": 1000
}

I'd like to use serde to work with these objects, with a struct like

struct MyObject {
  name: String,
  value: i32,
  bound: Range<i32>,
}

Without any modifications, serializing one of these structs yields

{
  "name": "foo",
  "value": 1234,
  "bound": {
    "start": 1000,
    "end": 5000
  }
}

I can apply #[serde(flatten)] to get closer, yielding

{
  "name": "foo",
  "value": 1234,
  "start": 1000,
  "end": 5000
}

But adding #[serde(rename...)] doesn't seem to change anything, no matter what kind of arguments I try giving to the rename. Is it possible to flatten the range, and rename the args?


Solution

  • You can use serde attribute with and just use a intermediate structure letting the real implementation to serde:

    use core::ops::Range;
    use serde::{Deserialize, Serialize};
    use serde_json::Error;
    
    #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
    struct Foo {
        name: String,
        value: i32,
        #[serde(with = "range_aux", flatten)]
        bound: Range<i32>,
    }
    
    mod range_aux {
        use core::ops::Range;
        use serde::{Deserialize, Deserializer, Serialize, Serializer};
    
        #[derive(Serialize, Deserialize)]
        struct RangeAux {
            upper_bound: i32,
            lower_bound: i32,
        }
    
        pub fn serialize<S>(range: &Range<i32>, ser: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            RangeAux::serialize(
                &RangeAux {
                    upper_bound: range.end,
                    lower_bound: range.start,
                },
                ser,
            )
        }
    
        pub fn deserialize<'de, D>(d: D) -> Result<Range<i32>, D::Error>
        where
            D: Deserializer<'de>,
        {
            let range_aux: RangeAux = RangeAux::deserialize(d)?;
            Ok(Range {
                start: range_aux.lower_bound,
                end: range_aux.upper_bound,
            })
        }
    }
    
    fn main() -> Result<(), Error> {
        let data = r#"{"name":"foo","value":1234,"upper_bound":5000,"lower_bound":1000}"#;
    
        let foo: Foo = serde_json::from_str(data)?;
    
        assert_eq!(
            foo,
            Foo {
                name: "foo".to_string(),
                value: 1234,
                bound: 1000..5000
            }
        );
    
        let output = serde_json::to_string(&foo)?;
    
        assert_eq!(data, output);
    
        Ok(())
    }
    

    That very close to remote pattern but this doesn't work with generic see serde#1844.

    A possible generic version:

    use core::ops::Range;
    use serde::{Deserialize, Serialize};
    use serde_json::Error;
    
    #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
    struct Foo {
        name: String,
        value: i32,
        #[serde(with = "range_aux", flatten)]
        bound: Range<i32>,
    }
    
    mod range_aux {
        use core::ops::Range;
        use serde::{Deserialize, Deserializer, Serialize, Serializer};
    
        pub fn serialize<S, Idx: Serialize>(range: &Range<Idx>, ser: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            // could require Idx to be Copy or Clone instead of borrowing Idx
            #[derive(Serialize)]
            struct RangeAux<'a, Idx> {
                upper_bound: &'a Idx,
                lower_bound: &'a Idx,
            }
            RangeAux::serialize(
                &RangeAux {
                    upper_bound: &range.end,
                    lower_bound: &range.start,
                },
                ser,
            )
        }
    
        pub fn deserialize<'de, D, Idx: Deserialize<'de>>(d: D) -> Result<Range<Idx>, D::Error>
        where
            D: Deserializer<'de>,
        {
            #[derive(Deserialize)]
            struct RangeAux<Idx> {
                upper_bound: Idx,
                lower_bound: Idx,
            }
            let range_aux: RangeAux<Idx> = RangeAux::deserialize(d)?;
            Ok(Range {
                start: range_aux.lower_bound,
                end: range_aux.upper_bound,
            })
        }
    }
    
    fn main() -> Result<(), Error> {
        let data = r#"{"name":"foo","value":1234,"upper_bound":5000,"lower_bound":1000}"#;
    
        let foo: Foo = serde_json::from_str(data)?;
    
        assert_eq!(
            foo,
            Foo {
                name: "foo".to_string(),
                value: 1234,
                bound: 1000..5000
            }
        );
    
        let output = serde_json::to_string(&foo)?;
    
        assert_eq!(data, output);
    
        Ok(())
    }