Serde Json visitor to derserialise into generic struct - rust

I have a nested JSON structure, the relevant bits of which look like this:
{
"params": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer",
"choices": [
0,
26,
4
]
},
"foobar": {
"type": "decimal"
},
"foobaz": {
"type": "decimal",
"choices": [
0.1,
26.6,
4
]
}
}
}
Or, in other words, a map of objects, each of which specifies a field containing its 'type' (integer or decimal), and an optional array of choices.
I am trying to deserialise this into a rust struct like this:
#[derive(Debug)]
pub struct OrderParameter<S> {
choices: Option<Vec<S>>,
}
Where S is either a rust Decimal (from rust_decimal) or an isize. i.e., the parameters should end up looking like this (pseudocode)
foo: OrderParameter<isize> {choices: None}
bar: OrderParameter<isize> {choices: Some(Vec(0_i, 26_i,...)}
foobar: OrderParameter<Decimal> {choices: None}
foobaz: OrderParameter<Decimal> {choices: Some(Vec(Decimal(0,1)...)}
I have been referring to this: https://serde.rs/deserialize-struct.html But I'm struggling to get the generic part to work. This is what I have so far:
use super::errors::ParseError;
use rust_decimal::prelude::*;
use serde::{de, de::MapAccess, de::SeqAccess, de::Visitor, Deserialize, Deserializer, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
#[derive(PartialEq)]
pub enum OrderParameterType {
Integer,
Decimal,
}
impl FromStr for OrderParameterType {
type Err = ParseError;
fn from_str(v: &str) -> Result<Self, Self::Err> {
match v {
"integer" => Ok(Self::Integer),
"decimal" => Ok(Self::Decimal),
_ => Err(ParseError),
}
}
}
#[derive(Debug)]
pub struct OrderParameter<S> {
choices: Option<Vec<S>>,
}
impl<'de, S> Deserialize<'de> for OrderParameter<S> { // Compiler suggests: consider restricting type parameter `S`: `: parser::types::participant::_::_serde::Deserialize<'_>`, but adding S: Deserialize<'de> to the where block results in: impl has stricter requirements than trait impl has extra requirement `S: Deserialize<'de>`
fn deserialize<D>(deserializer: D) -> Result<OrderParameter<S>, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
Variant,
Choices,
}
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("`type` or `choices`")
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
{
match value {
"type" => Ok(Field::Variant),
"choices" => Ok(Field::Choices),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct OrderParameterVisitor<T> {
marker: PhantomData<fn() -> OrderParameter<T>>,
}
impl<T> OrderParameterVisitor<T> {
fn new() -> Self {
Self {
marker: PhantomData,
}
}
}
impl<'de, T> Visitor<'de> for OrderParameterVisitor<T>
where
T: Deserialize<'de>,
{
type Value = OrderParameter<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct OrderParameter")
}
fn visit_map<V>(self, mut map: V) -> Result<OrderParameter<T>, V::Error>
where
V: MapAccess<'de>,
{
let mut variant = None;
let mut choices: Option<Vec<T>>;
while let Some(key) = map.next_key()? {
match key {
Field::Variant => {
if variant.is_some() {
return Err(de::Error::duplicate_field("type"));
}
variant =
Some(OrderParameterType::from_str(map.next_value()?).unwrap());
}
Field::Choices => {
if choices.is_some() {
return Err(de::Error::duplicate_field("choices"));
}
choices = Some(map.next_value()?);
}
}
}
let variant = variant.ok_or_else(|| de::Error::missing_field("type"))?;
if choices.is_some() {
if variant == OrderParameterType::Decimal {
let mapped_choices: Vec<Decimal>;
mapped_choices = Some(choices)
.iter()
.map(|x| Decimal::from_str(x).unwrap())
.collect::<Vec<Decimal>>()
} else if variant == OrderParameterType::Integer {
let mapped_choices: Vec<isize> = Vec::from([0, 1]);
} else if variant == OrderParameterType::Wallet {
choices = None
} else if variant == OrderParameterType::Participant {
choices = None
}
}
Ok(OrderParameter { choices })
}
}
const FIELDS: &'static [&'static str] = &["type", "choices"];
deserializer.deserialize_struct("OrderParameter", FIELDS, OrderParameterVisitor::new())
}
}

Are you sure you really want generics? I think you might actually want similarly structured enum variants. Remarkably, you need neither generics nor custom (de)serialization logic for this — serde on its own contains all the machinery you need to derive the relevant traits.
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Serialize, Deserialize)]
struct Params {
params: BTreeMap<String, Item>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
enum Item {
Integer {
#[serde(default)]
choices: Option<Vec<i32>>,
},
Decimal {
#[serde(default)]
choices: Option<Vec<Decimal>>,
},
}
fn main() {
let json = r#"
{
"params": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer",
"choices": [
0,
26,
4
]
},
"foobar": {
"type": "decimal"
},
"foobaz": {
"type": "decimal",
"choices": [
0.1,
26.6,
4
]
}
}
}"#;
let items = serde_json::from_str::<Params>(json).unwrap().params;
println!("{:#?}", items);
}
Which gets you
{
"bar": Integer {
choices: Some(
[
0,
26,
4,
],
),
},
"foo": Integer {
choices: None,
},
"foobar": Decimal {
choices: None,
},
"foobaz": Decimal {
choices: Some(
[
0.1,
26.6,
4,
],
),
},
}

Related

How to reuse common codes for structs?

I want to reuse codes for structs. For example:
use std::fmt::Display;
struct CommonStruct<T: Display> {
// could have more fields
data: T
}
struct A<T: Display> {
com: CommonStruct<T>,
age: i32
}
struct B<T: Display> {
com: CommonStruct<T>,
name: String
}
impl<T: Display> A<T> {
// could be more common functions
fn print_data(&self) {
// could be more complicated
println!("data: {}", self.com.data);
}
}
impl<T: Display> B<T> {
// could be more common functions
fn print_data(&self) {
// could be more complicated
println!("data: {}", self.com.data);
}
}
fn main() {
let a = A{ com: CommonStruct{data: 10}, age: 0 };
a.print_data();
let b = B{ com: CommonStruct{data: 12}, name: "123".to_string() };
b.print_data();
}
where A and B have some common fields packed by CommonStruct and some common functions (e.g., print_data).
I tried to use trait but cannot figure out a solution:
use std::fmt::Display;
struct CommonStruct<T: Display> {
// could have more fields
data: T
}
struct A<T: Display> {
com: CommonStruct<T>,
age: i32
}
struct B<T: Display> {
com: CommonStruct<T>,
name: String
}
trait Common {
// could be more common functions
fn print_data(&self) {
print_data(&self)
}
}
impl<T: Display> Common for A<T> {
}
impl<T: Display> Common for B<T> {
}
fn print_data(t: &Common) {
// could be more complicated
println!("data: {}", t.com.data);
}
fn main() {
let a = A{ com: CommonStruct{data: 10}, age: 0 };
a.print_data();
let b = B{ com: CommonStruct{data: 12}, name: "123".to_string() };
b.print_data();
}
Since print_data only uses the CommonStruct, and A and B share no other fields, make it an implementation of CommonStruct and call it directly.
impl <T: Display> CommonStruct<T> {
fn print_data(&self) {
println!("data: {}", self.data);
}
}
fn main() {
let a = A{ com: CommonStruct{data: 10}, age: 0 };
a.com.print_data();
let b = B{ com: CommonStruct{data: 12}, name: "123".to_string() };
b.com.print_data();
}
Alternatively, make a trait which has a concrete implementation of print_data which relies on a method to get the data.
trait HasData<T: Display> {
fn get_data(&self) -> &T;
fn print_data(&self) {
// could be more complicated
println!("data: {}", self.get_data());
}
}
Then each only has to implement how to get the data.
impl<T: Display> HasData<T> for CommonStruct<T> {
fn get_data(&self) -> &T {
return &self.data;
}
}
impl<T: Display> HasData<T> for A<T> {
fn get_data(&self) -> &T {
return &self.com.data;
}
}
impl<T: Display> HasData<T> for B<T> {
fn get_data(&self) -> &T {
return &self.com.data;
}
}
fn main() {
let a = A{ com: CommonStruct{data: 1}, age: 0 };
a.print_data();
let b = B{ com: CommonStruct{data: 2}, name: "123".to_string() };
b.print_data();
let c = CommonStruct{data: 3};
c.print_data();
}
I saw that you wanted to use traits. I've included a working example below.
However, traits are used to implement shared behavior. A trait must be implemented on each type you wish to have the shared behavior. So, you do have to impl a separate print_data function for a given type.
use std::fmt::Display;
// `Common` structure of some type `T`.
struct Common<T: Display> {
data: T,
}
// `Printable` trait used to print `data`.
trait Printable {
fn print_data(&self);
}
struct A {
common: Common<String>,
}
impl A {
fn new(s: &str) -> A {
A {
common: Common { data: s.to_owned() },
}
}
}
// Implement `Printable the trait for A`.
impl Printable for A {
fn print_data(&self) {
println!("A.common.data: {}", self.common.data)
}
}
struct B {
common: Common<i32>,
}
// Implement the `Printable` trait for `B`.
impl Printable for B {
fn print_data(&self) {
println!("B.common.data: {}", self.common.data)
}
}
So that's traits, but if you must call the same function to print the data, then maybe something like the following can work for you. It defines an enum with three variants. You can then match on a particular variant as demonstrated by print_all_data.
use std::path::PathBuf;
struct G {
path: PathBuf,
common: Common<String>,
}
enum C {
D(A),
E(B),
F(G),
}
fn print_all_data(c: C) {
match c {
C::D(a) => println!("{}", a.common.data),
C::E(b) => println!("{}", b.common.data),
C::F(g) => println!("{} {:?}", g.common.data, g.path)
}
}
fn main() {
let a = A::new("hello");
a.print_data();
let b = B {
common: Common { data: 42 },
};
b.print_data();
let g = G {
path: PathBuf::from("some/path/file.txt"),
common: Common {data: "some_data".to_owned()}
};
let cfg = C::F(g);
print_all_data(cfg);
print_all_data(C::D(a));
print_all_data(C::E(b));
}

How to ignore partial items of a sequence

Instead of defining a field as optional and collect both Nones and Somes like the following:
extern crate serde;
extern crate serde_json;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
struct Bar {
a: u32,
b: Option<u32>,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Foo {
vec: Vec<Bar>,
}
fn main() {
let data = r#"{ "vec": [ { "a": 1 }, { "a": 2, "b": 3 } ] }"#;
assert_eq!(
serde_json::from_str::<Foo>(data).unwrap(),
Foo {
vec: vec![Bar { a: 1, b: None }, Bar { a: 2, b: Some(3) }]
}
);
}
It would be nice to only collect fully defined elements so the Bar struct could be defined as struct Bar { a: u32, b: u32 } and serde_json::from_str would simply return Foo { vec: [ Bar { a: 2, b: 3 } ] }.
How to achieve such behavior? Here is my unsuccessful attempt to create a custom Deserialize implementation that tries to tackle this.
extern crate serde;
extern crate serde_json;
use core::fmt;
use serde::{
de::{SeqAccess, Visitor},
Deserialize, Deserializer,
};
#[derive(Debug, Deserialize)]
struct Bar {
a: i32,
b: i32,
c: i32,
}
#[derive(Debug)]
struct VecOpt(Vec<Bar>);
impl<'de> Deserialize<'de> for VecOpt {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ArrayVecVisitor;
impl<'de> Visitor<'de> for ArrayVecVisitor {
type Value = VecOpt;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "VecOpt")
}
fn visit_seq<SA>(self, mut seq: SA) -> Result<Self::Value, SA::Error>
where
SA: SeqAccess<'de>,
{
let mut values = Vec::new();
loop {
match seq.next_element::<Bar>() {
Ok(Some(x)) => values.push(x),
Ok(None) => break,
// If error, the input reader won't move to the next element of the sequence and the following `seq.next_element` will
// simply try to read contents of the current element. In this case, `"c": 5 }, ... `
Err(_) => {}
}
}
Ok(VecOpt(values))
}
}
deserializer.deserialize_seq(ArrayVecVisitor)
}
}
#[derive(Debug, Deserialize)]
struct Foo {
vec: VecOpt,
}
fn main() {
let data = r#"{ "vec": [ { "a": 1 "b": 2, "c": 3 }, { "b": 4, "c": 5 }, { "a": 6 "b": 7, "c": 8 } ] }"#;
dbg!(serde_json::from_str::<Foo>(data).unwrap());
}
Your code worked for the most part but invalid JSON literal in the main, which missed couple commas, tripped it over. The problem is that the Err(_) branch swallowed all errors indiscriminately; it should have only silenced missing field error:
fn visit_seq<SA>(self, mut seq: SA) -> Result<Self::Value, SA::Error>
where
SA: SeqAccess<'de>,
{
let mut values = Vec::new();
loop {
match seq.next_element::<Bar>() {
Ok(Some(x)) => values.push(x),
Ok(None) => break,
Err(e) => {
if !e.to_string().starts_with("missing field") {
return Err(e);
}
}
}
}
Ok(VecOpt(values))
}
TBH I really don't like this solution although it worked. Option is a perfect fit here for modelling the potentially missing fields.

How do I modify the JSON output for a Result<T,E> serialized with serde?

A simple code:
use serde::Serialize;
#[derive(Serialize)]
struct MyStruct {
foo: Result<u32, String>
}
fn main() {
let m = MyStruct {
foo: Ok(43)
};
let n = MyStruct {
foo: Err("oh no!".into())
};
println!("{}", serde_json::to_string_pretty(&m).unwrap());
println!("{}", serde_json::to_string_pretty(&n).unwrap());
}
This outputs (playground):
{
"foo": {
"Ok": 43
}
}
{
"foo": {
"Err": "oh no!"
}
}
Can I modify the serializer to have a custom output for Result<T,E>? I would like something like:
// No "Ok" field in case of Ok(T)
{
"foo": 43
}
// Rename "Err" to "error" in case of Err(E)
{
"foo": {
"error": "oh no!"
}
}
Serde attributes are not powerful enough to do the transformation from the default Result serialization to what you want, so you will need to write custom serialization. Fortunately, it is quite simple:
use serde::{Serialize, Serializer, ser::SerializeMap};
struct MyStruct {
foo: Result<u32, String>
}
impl Serialize for MyStruct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
match &self.foo {
Ok(value) => map.serialize_entry("foo", &value)?,
Err(error) => map.serialize_entry("foo", &MyError { error } )?,
}
map.end()
}
}
// This is just used internally to get the nested error field
#[derive(Serialize)]
struct MyError<E> {
error: E,
}

Can't capture dynamic environment in a fn item when implementing serde::Deserialize for JSON

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)),
}
}
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:
What does the rust compiler "error: can't capture dynamic environment in a fn item; use the || { ... } closure form instead" mean, and how to fix it?
Unable to create a local function because "can't capture dynamic environment in a fn item"
Can't capture dynamic environment in a fn item about iron lib
Can't capture dynamic environment in a fn item
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:
How do I serialize an enum without including the name of the enum variant?
How do I store a result using Serde Zero-copy deserialization of a Futures-enabled Hyper Chunk?
Why can Serde not derive Deserialize for a struct containing only a &Path?
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

Is there a way to omit wrapper/root objects when deserializing objects with Serde?

I have the following object:
{
"data": {
"id": 1,
"name": "South America",
"countries": {
"data": [
{
"id": 122,
"name": "Brazil",
"capital": "Brasilia"
}
]
}
}
}
I'd like to define two structs, Continent and Country, omitting the data wrappings which don't add value.
I would implement this using a wrapper struct that can be used directly for dropping the top level of nesting as well as through a #[serde(with = "...")] attribute for eliminating levels of nesting within the deserialized data structure.
use serde::{Deserialize, Deserializer};
#[derive(Deserialize, Debug)]
struct Continent {
id: u64,
name: String,
#[serde(with = "Wrapper")]
countries: Vec<Country>,
}
#[derive(Deserialize, Debug)]
struct Country {
id: u64,
name: String,
capital: String,
}
#[derive(Deserialize)]
struct Wrapper<T> {
data: T,
}
impl<T> Wrapper<T> {
fn deserialize<'de, D>(deserializer: D) -> Result<T, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
let wrapper = <Self as Deserialize>::deserialize(deserializer)?;
Ok(wrapper.data)
}
}
fn main() -> serde_json::Result<()> {
let j = r#"
{
"data": {
"id": 1,
"name": "South America",
"countries": {
"data": [
{
"id": 122,
"name": "Brazil",
"capital": "Brasilia"
}
]
}
}
}"#;
let wrapper: Wrapper<Continent> = serde_json::from_str(j)?;
println!("{:#?}", wrapper.data);
Ok(())
}
There are three materially different places where insignificant nesting arises:
adjacent to other fields
by itself at the top level
by itself below the top level
All three require different approaches. #2 and #3 are observed in this question.
To solve #1, see Is it possible to flatten sub-object fields while parsing with serde_json?

Resources