5

So I have a JSON that looks like this :

{
  "name": "customer",
  "properties": [
    {
      "name": "id",
      "type": "int",
      "value": 32
    },
    {
      "name": "name",
      "type": "string",
      "value": "John"
    }
  ]
}

Currently I am deserializing to this set of struct :

#[derive(Serialize, Deserialize, Debug)]
struct Customer {
    name: String,
    properties: Vec<Property>,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "name", content = "value")]
enum Property {
    #[serde(rename = "id")]
    Id(i32),
    #[serde(rename = "name")]
    Name(String),
}

But to avoid dealing with matching over enum every time I want to access a property I would like to deserialize it to a struct that looks like this :

struct Customer {
  name: String,
  properties: Properties,
}

struct Properties {
  id: i32, // will be 32 as in the object containing the name "id".
  name: String, // will be John as in the object containing the name "name".
}

Is this something that the serde library allow in some way ? If so could you provide an example on how to achieve that ?

Note that I can't mess with the actual json structure so I am not interested in any solutions that requires that.

3 Answers 3

3

By using a custom deserializer as follows

extern crate serde_json; // 1.0.32
extern crate serde; // 1.0.80
#[macro_use] extern crate serde_derive;

use serde::de::{Deserializer, SeqAccess, Visitor};
use std::fmt;


#[derive(Serialize, Deserialize)]
struct Customer {
    name: String,
    #[serde(deserialize_with = "parse_property")]
    properties: Property,
}

// #[derive(Default, Debug, Deserialize)]
#[derive(Default, Serialize, Deserialize, Debug)]
struct Property {
    id: i32,
    name: String,
}

#[derive(Default, Serialize, Deserialize, Debug)]
struct Key {
    name: String,
    value: i32,
}

#[derive(Default, Serialize, Deserialize, Debug)]
struct Val {
    name: String,
    value: String
}

fn parse_property<'de, D>(deserializer: D) -> Result<Property, D::Error>
where
    D: Deserializer<'de>,
{
    struct PropertyParser;
    impl<'de> Visitor<'de> for PropertyParser
    {
        type Value = Property;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("expect [key, val]")
        }
        
        fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
            println!("In custom deserializer");
            let mut prop = Property { ..Default::default() };
            
            let tmp = seq.next_element::<Key>()?;
            if let Some(a) = tmp {
                prop.id = a.value;
            };
            
            let tmp = seq.next_element::<Val>()?;
            if let Some(b) = tmp {
                prop.name = b.value;
            };
            
            Ok(prop)
        }
    }

    deserializer.deserialize_any(PropertyParser{})
}


fn main() {
    println!("Hello, world!");
    let data = r#"
        {
  "name": "customer",
  "properties": [
    {
      "name": "id",
      "type": "int",
      "value": 32
    },
    {
      "name": "name",
      "type": "string",
      "value": "John"
    }
  ]
}"#;

    let p: Customer = serde_json::from_str(data).unwrap();

    println!("Please call {} at the number {} {}", p.name, p.properties.id, p.properties.name);

}

playground

Sign up to request clarification or add additional context in comments.

Comments

1

Thanks to edkeveked's answer I managed to find a solution that fits my needs pretty well.

Basically I rearranged the Deserializer to loop over the whole properties array and try to match every object in it with an Enum variant. I like this because I can easily map a new property in the future if it comes to it and it feels more flexible type-wise.

Anyway, here's the code for it :

extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use serde::de::{Deserializer, SeqAccess, Visitor};
use std::fmt;

#[derive(Serialize, Deserialize, Debug)]
struct Customer {
    name: String,
    #[serde(deserialize_with = "parse_property")]
    properties: CustomerProps,
}

#[derive(Default, Serialize, Deserialize, Debug)]
struct CustomerProps {
    id: i32,
    name: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "name", content = "value")]
enum Property {
    #[serde(rename = "id")]
    Id(i32),
    #[serde(rename = "name")]
    Name(String),
}

fn parse_property<'de, D>(deserializer: D) -> Result<CustomerProps, D::Error>
where
    D: Deserializer<'de>,
{
    struct PropertyParser;
    impl<'de> Visitor<'de> for PropertyParser {
        type Value = CustomerProps;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("[u64, f32, usize]")
        }

        fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
            let mut prop = CustomerProps {
                ..Default::default()
            };
            while let Some(tmp) = seq.next_element::<Property>()? {
                match tmp {
                    Property::Id(id) => prop.id = id,
                    Property::Name(name) => prop.name = name,
                }
            }
            Ok(prop)
        }
    }
    deserializer.deserialize_any(PropertyParser {})
}

fn main() {
    let data = r#"{
        "name": "customer",
        "properties": [
            {
                "name": "id",
                "type": "int",
                "value": 32
            },
            {
                "name": "name",
                "type": "string",
                "value": "John"
            }
        ]
    }"#;

    let p: Customer = serde_json::from_str(data).unwrap();

    println!("Please call {} at the number {} {}", p.name, p.properties.id, p.properties.name);
}

Comments

-2

I think you can use generic data types. You can find more information about that on the book

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.