2

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?

breadjesus
  • 1,979
  • 3
  • 13
  • 8

2 Answers2

2

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(())
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
1

Not necessarily more concise than a custom serializer, but certainly a good bit more trivial is a solution with [serde(from and into)]. (I feel like I'm posting this on every serde question. :/)

You define an auxiliary, serializable struct that has the JSON structure you want:

#[derive(Deserialize, Serialize, Clone)]
struct AuxMyObject {
    name: String,
    value: i32,
    upper_bound: i32,
    lower_bound: i32,
}

Then you explain to rust how your auxiliary struct relates to the original struct. It's a bit tedious (but easy), there may be some macro crates that help lessen the typing load:

impl From<MyObject> for AuxMyObject {
    fn from(from: MyObject) -> Self {
        Self {
            name: from.name,
            value: from.value,
            lower_bound: from.bound.start,
            upper_bound: from.bound.end,
        }
    }
}

impl From<AuxMyObject> for MyObject {
    fn from(from: AuxMyObject) -> Self {
        Self {
            name: from.name,
            value: from.value,
            bound: Range {
                start: from.lower_bound,
                end: from.upper_bound,
            },
        }
    }
}

Lastly, you tell serde to replace your main struct with the auxiliary struct when serializing:

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(from = "AuxMyObject", into = "AuxMyObject")]
struct MyObject { … }

Playground

Caesar
  • 6,733
  • 4
  • 38
  • 44
  • 1
    I didn't know this, con is: that not reusable, pro: simple – Stargateur Jun 03 '22 at 04:11
  • 1
    There's another con: It'll probably copy the string. – Caesar Jun 03 '22 at 04:34
  • It won't. `String` is moved, not copied. – Cerberus Jun 03 '22 at 04:58
  • @Cerberus `serde_json::to_string` takes a `&MyObject`. The only way serde can make an `AuxMyObject` from that is by `.clone().into()`. (Which is also why removing the `#[derive(Clone)]` will fail.) – Caesar Jun 03 '22 at 05:19
  • Well, but it will be this way whenever we use `AuxMyObject` or the `MyObject` directly, or I'm missing something? – Cerberus Jun 03 '22 at 05:24
  • I think you are indeed missing something. Calling `serde_json::to_string` on `MyObject`/`Foo` with Stargateur's solution will not make a clone of `Foo::name`, right? My solution will make a clone. – Caesar Jun 03 '22 at 05:29
  • yep clone for you that sure, I wonder why serde didn't make it `&Object` and directly force `Object` – Stargateur Jun 03 '22 at 05:47
  • I'm still not sure what do you mean. If we have a `String` inside struct and want to serialize this struct, we have to copy the contents of this string into serialized data. If we have two structs which both hold `String` and convert between them with `From`/`Into`, the string isn't copied, it's moved. What's the difference, then? – Cerberus Jun 03 '22 at 05:53
  • @Cerberus [Chat](https://chat.stackoverflow.com/transcript/message/54686634#54686634)? – Caesar Jun 03 '22 at 05:54