3

I'm trying to manually deserialize a struct that can use the same JSON attribute as different JSON types (e.g. object or string). For example:

[
  {
    "Name": "a single unit param",
    "Units": "m/s"
  },
  {
    "Name": "a multi unit param",
    "Units": {
      "Metric": {
        "Units": "m/s"
      },
      "Imperial": {
        "Units": "ft/s"
      }
    }
  }
]

What I have so far is below. I don't have enough experience in Rust to figure out if what I'm trying to do is possible.

use serde::de::{self, MapAccess, Visitor};
use serde::{Deserialize, Deserializer}; // 1.0.91
use std::fmt;

#[derive(Debug, Deserialize)]
pub struct SingleUnitParam {
    name: String,
    units: String,
}

#[derive(Debug, Deserialize)]
pub struct UnitInfo {
    units: String,
}

#[derive(Debug, Deserialize)]
pub struct MultiUnits {
    metric: UnitInfo,
    imperial: UnitInfo,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum StrOrUnitsObj<'a> {
    Str(&'a str),
    UnitsObj(MultiUnits),
}

#[derive(Debug, Deserialize)]
pub struct MultiUnitParam {
    name: String,
    units: MultiUnits,
}

#[derive(Debug)]
pub enum Param {
    Single(SingleUnitParam),
    Multiple(MultiUnitParam),
}

impl<'de> Deserialize<'de> for Param {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        enum Field {
            Name,
            UnitsAsObj,
            UnitsAsStr,
        };

        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
            where
                D: Deserializer<'de>,
            {
                struct FieldVisitor;

                impl<'de> Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                        formatter.write_str("`Name` or `Units`")
                    }

                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
                    where
                        E: de::Error,
                    {
                        match value {
                            "Name" => Ok(Field::Name),
                            "Units" => Ok({
                                let val = StrOrUnitsObj::deserialize(deserializer).unwrap();

                                match val {
                                    StrOrUnitsObj::Str(s) => Field::UnitsAsObj,
                                    StrOrUnitsObj::UnitsObj(obj) => Field::UnitsAsStr,
                                }
                            }),
                            _ => Err(de::Error::unknown_field(value, FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct ParamVisitor;

        impl<'de> Visitor<'de> for ParamVisitor {
            type Value = Param;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("enum Param")
            }

            fn visit_map<V>(self, mut map: V) -> Result<Param, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut name = None;
                let mut units_as_string = None;
                let mut units_as_object = None;
                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Name => {
                            if name.is_some() {
                                return Err(de::Error::duplicate_field("Name"));
                            }
                            name = Some(map.next_value()?);
                        }
                        Field::UnitsAsObj => {
                            if units_as_object.is_some() {
                                return Err(de::Error::duplicate_field("Units"));
                            }
                            units_as_object = Some(map.next_value()?);
                        }
                        Field::UnitsAsStr => {
                            if units_as_string.is_some() {
                                return Err(de::Error::duplicate_field("Units"));
                            }
                            units_as_string = Some(map.next_value()?);
                        }
                    }
                }
                let name = name.ok_or_else(|| de::Error::missing_field("Name"))?;
                if let Some(units_as_object) = units_as_object {
                    Ok(Param::Multiple(MultiUnitParam {
                        name: name,
                        units: units_as_object,
                    }))
                } else {
                    let units_as_string =
                        units_as_string.ok_or_else(|| de::Error::missing_field("Units"))?;
                    Ok(Param::Single(SingleUnitParam {
                        name: name,
                        units: units_as_string,
                    }))
                }
            }
        }

        const FIELDS: &'static [&'static str] = &["Name", "Units"];
        deserializer.deserialize_struct("Param", FIELDS, ParamVisitor)
    }
}

fn main() {
    let json_raw = r#"[
            { "Name": "a single unit param", "Units": "m/s" },
            { "Name": "a multi unit param", "Units": { "Metric": { "Units": "m/s" }, "Imperial": { "Units": "ft/s" } } }
        ]"#;
    let j: Vec<Param> = serde_json::from_str(&json_raw).unwrap();
    match &j[0] {
        Param::Single(p) => {
            assert_eq!(p.name, "a single unit param");
            assert_eq!(p.units, "m/s");
        }
        Param::Multiple(_p) => panic!("Expected SingleUnitParam, actual MultiUnitParam"),
    }
    match &j[1] {
        Param::Single(_p) => panic!("Expected MultiUnitParam, actual SingleUnitParam"),
        Param::Multiple(p) => {
            assert_eq!(p.name, "a multi unit param");
            assert_eq!(p.units.metric.units, "m/s");
            assert_eq!(p.units.imperial.units, "ft/s");
        }
    }
}

playground

error[E0434]: can't capture dynamic environment in a fn item
  --> src/main.rs:74:70
   |
74 |                                 let val = StrOrUnitsObj::deserialize(deserializer).unwrap();
   |                                                                      ^^^^^^^^^^^^
   |
   = help: use the `|| { ... }` closure form instead

Is there a better way I can return a different Field result for a JSON key based on the JSON value type? Am I on the right track?

fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
    E: de::Error,
{
    match value {
        "Name" => Ok(Field::Name),
        "Units" => Ok({
            let val = StrOrUnitsObj::deserialize(deserializer).unwrap();

            match val {
                StrOrUnitsObj::Str(s) => {
                    Field::UnitsAsObj
                },
                StrOrUnitsObj::UnitsObj(obj) => {
                    Field::UnitsAsStr
                }
            }
        }),
        _ => Err(de::Error::unknown_field(value, FIELDS)),
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
E-rich
  • 9,243
  • 11
  • 48
  • 79
  • 3
    What is the reason that you have not followed the compilers advice and help text? – Shepmaster Jun 14 '19 at 19:00
  • I [tried several things](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c760a962a103731579f7b7c9130efcb2) but I ended up passing my threshold of 60 mins following the rabbit down the rabbit hole before asking for help. – E-rich Jun 15 '19 at 13:33

2 Answers2

1

I finally found a working solution, but I'm not sure if this is the idiomatic way of deserializing an array of polymorphic JSON objects.

TL;DR manually parse the JSON object in the visit_map for the ParamVisitor (i.e. not the FieldVisitor) so that we can check the parsed JSON for which type it is and set the respective variable based on the type.

use std::fmt;
use serde::{Deserialize, Deserializer}; // 1.0.91
use serde::de::{self, Visitor, MapAccess};

#[derive(Debug, Deserialize)]
pub struct SingleUnitParam {
    name: String,
    units: String,
}

#[derive(Debug, Deserialize)]
pub struct UnitInfo {
    #[serde(alias = "Units")]
    units: String,
}

#[derive(Debug, Deserialize)]
pub struct MultiUnits {
    #[serde(alias = "Metric")]
    metric: UnitInfo,
    #[serde(alias = "Imperial")]
    imperial: UnitInfo,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum StrOrUnitsObj<'a> {
    Str(&'a str),
    UnitsObj(MultiUnits)
}

#[derive(Debug, Deserialize)]
pub struct MultiUnitParam {
    name: String,
    units: MultiUnits,
}

#[derive(Debug)]
pub enum Param {
    Single(SingleUnitParam),
    Multiple(MultiUnitParam),
}

impl<'de> Deserialize<'de> for Param {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        enum Field { Name, Units/*, UnitsAsObj, UnitsAsStr*/ };

        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
            where
                D: Deserializer<'de>,
            {
                struct FieldVisitor;

                impl<'de> Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                        formatter.write_str("`Name` or `Units`")
                    }

                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
                    where
                        E: de::Error,
                    {
                        match value {
                            "Name" => Ok(Field::Name),
                            "Units" => Ok(Field::Units),
                            // Can't get access to the JSON value to inspect it here.
                            // "Units" => Ok({
                            //     let val = StrOrUnitsObj::deserialize(deserializer).unwrap();

                            //     match val {
                            //         StrOrUnitsObj::Str(s) => {
                            //             Field::UnitsAsObj
                            //         },
                            //         StrOrUnitsObj::UnitsObj(obj) => {
                            //             Field::UnitsAsStr
                            //         }
                            //     }
                            // }),
                            _ => Err(de::Error::unknown_field(value, FIELDS)),
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct ParamVisitor;

        impl<'de> Visitor<'de> for ParamVisitor {
            type Value = Param;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("enum Param")
            }

            fn visit_map<V>(self, mut map: V) -> Result<Param, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut name = None;
                let mut units_as_string = None;
                let mut units_as_object = None;
                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Name => {
                            if name.is_some() {
                                return Err(de::Error::duplicate_field("Name"));
                            }
                            name = Some(map.next_value()?);
                        }
                        Field::Units => {
                            if units_as_string.is_some() || units_as_object.is_some() {
                                return Err(de::Error::duplicate_field("Units"));
                            }
                            // Here is where we can get the JSON value and check its type.
                            let v: serde_json::Value = map.next_value()?;
                            if v.is_object() {
                                let v: MultiUnits = serde_json::from_value(v).unwrap();
                                units_as_object = Some(v);
                            } else if v.is_string() {
                                units_as_string = Some(v.as_str().unwrap().to_owned());
                            }
                        }
                        // Field::UnitsAsObj => {
                        //     if units_as_object.is_some() {
                        //         return Err(de::Error::duplicate_field("Units"));
                        //     }
                        //     units_as_object = Some(map.next_value()?);
                        // }
                        // Field::UnitsAsStr => {
                        //     if units_as_string.is_some() {
                        //         return Err(de::Error::duplicate_field("Units"));
                        //     }
                        //     units_as_string = Some(map.next_value()?);
                        // }
                    }
                }
                let name = name.ok_or_else(|| de::Error::missing_field("Name"))?;
                if let Some(units_as_object) = units_as_object {
                    Ok(Param::Multiple(MultiUnitParam {
                        name: name,
                        units: units_as_object
                    }))
                } else {
                    let units_as_string = units_as_string.ok_or_else(|| de::Error::missing_field("Units"))?;
                    Ok(Param::Single(SingleUnitParam {
                        name: name,
                        units: units_as_string
                    }))
                }
            }
        }

        const FIELDS: &'static [&'static str] = &["Name", "Units"];
        deserializer.deserialize_struct("Param", FIELDS, ParamVisitor)
    }
}

fn main() {
    let json_raw = r#"[
        { "Name": "a single unit param", "Units": "m/s" },
        { "Name": "a multi unit param", "Units": { "Metric": { "Units": "m/s" }, "Imperial": { "Units": "ft/s" } } }
    ]"#;
    let j: Vec<Param> = serde_json::from_str(&json_raw).unwrap();
    match &j[0] {
        Param::Single(p) => {
            assert_eq!(p.name, "a single unit param");
            assert_eq!(p.units, "m/s");
        },
        Param::Multiple(_p) => panic!("Expected SingleUnitParam, actual MultiUnitParam")
    }
    match &j[1] {
        Param::Single(_p) => panic!("Expected MultiUnitParam, actual SingleUnitParam"),
        Param::Multiple(p) => {
            assert_eq!(p.name, "a multi unit param");
            assert_eq!(p.units.metric.units, "m/s");
            assert_eq!(p.units.imperial.units, "ft/s");
        }
    }
}

playground

halfer
  • 19,824
  • 17
  • 99
  • 186
E-rich
  • 9,243
  • 11
  • 48
  • 79
1

Your problem arises because you are attempting to use a variable from an outer function in an inner function:

fn outer(id: i32) {
    fn inner() {
        println!("{}", id);
    }
}

You simply cannot do this:

error[E0434]: can't capture dynamic environment in a fn item
 --> src/lib.rs:3:24
  |
3 |         println!("{}", id);
  |                        ^^
  |
  = help: use the `|| { ... }` closure form instead

See also:


You don't need to write any custom deserialization at all; using Serde's attributes is powerful enough:

use serde::Deserialize; // 1.0.91
use serde_json; // 1.0.39

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Param<'a> {
    name: &'a str,
    #[serde(borrow)]
    units: Units<'a>,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Units<'a> {
    Str(&'a str),
    #[serde(borrow)]
    Multi(Multi<'a>),
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct Multi<'a> {
    #[serde(borrow)]
    metric: SingleUnit<'a>,
    #[serde(borrow)]
    imperial: SingleUnit<'a>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
struct SingleUnit<'a> {
    units: &'a str,
}

fn main() {
    let json_text = r#"[
      {
        "Name": "a single unit param",
        "Units": "m/s"
      },
      {
        "Name": "a multi unit param",
        "Units": {
          "Metric": {
            "Units": "m/s"
          },
          "Imperial": {
            "Units": "ft/s"
          }
        }
      }
    ]"#;

    let x: Vec<Param<'_>> = serde_json::from_str(json_text).expect("Bad schema");

    println!("{:?}", x);
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366