How can I use Serde with a JSON array with different objects for successes and errors?

Here's one way of doing that:

#[macro_use]
extern crate serde_derive; // 1.0.117
extern crate serde; // 1.0.117
extern crate serde_json; // 1.0.59

#[derive(Serialize, Deserialize, Debug)]
pub struct MyError {
    error: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MyAge {
    age: i32,
    name: String,
}

#[derive(Debug)]
enum AgeOrError {
    Age(MyAge),
    Error(MyError),
}

impl serde::Serialize for AgeOrError {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        match self {
            &AgeOrError::Age(ref my_age) => serializer.serialize_some(my_age),
            &AgeOrError::Error(ref my_error) => serializer.serialize_some(my_error),
        }
    }
}

enum AgeOrErrorField {
    Age,
    Name,
    Error,
}

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

        impl<'de> serde::de::Visitor<'de> for AgeOrErrorFieldVisitor {
            type Value = AgeOrErrorField;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(formatter, "age or error")
            }

            fn visit_str<E>(self, value: &str) -> Result<AgeOrErrorField, E>
            where
                E: serde::de::Error,
            {
                Ok(match value {
                    "age" => AgeOrErrorField::Age,
                    "name" => AgeOrErrorField::Name,
                    "error" => AgeOrErrorField::Error,
                    _ => panic!("Unexpected field name: {}", value),
                })
            }
        }

        deserializer.deserialize_any(AgeOrErrorFieldVisitor)
    }
}

impl<'de> serde::Deserialize<'de> for AgeOrError {
    fn deserialize<D>(deserializer: D) -> Result<AgeOrError, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_map(AgeOrErrorVisitor)
    }
}

struct AgeOrErrorVisitor;

impl<'de> serde::de::Visitor<'de> for AgeOrErrorVisitor {
    type Value = AgeOrError;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(formatter, "age or error")
    }

    fn visit_map<A>(self, mut map: A) -> Result<AgeOrError, A::Error>
    where
        A: serde::de::MapAccess<'de>,
    {
        let mut age: Option<i32> = None;
        let mut name: Option<String> = None;
        let mut error: Option<String> = None;
        loop {
            match map.next_key()? {
                Some(AgeOrErrorField::Age) => age = map.next_value()?,
                Some(AgeOrErrorField::Name) => name = map.next_value()?,
                Some(AgeOrErrorField::Error) => error = map.next_value()?,
                None => break,
            }
        }
        if let Some(error) = error {
            Ok(AgeOrError::Error(MyError { error: error }))
        } else {
            Ok(AgeOrError::Age(MyAge {
                age: age.expect("!age"),
                name: name.expect("!name"),
            }))
        }
    }
}

fn get_results(ages: &[i32]) -> Vec<AgeOrError> {
    let mut results = Vec::with_capacity(ages.len());
    for &age in ages.iter() {
        if age < 100 && age > 0 {
            results.push(AgeOrError::Age(MyAge {
                age: age,
                name: String::from("The dude"),
            }));
        } else {
            results.push(AgeOrError::Error(MyError {
                error: format!("{} is invalid age", age),
            }));
        }
    }
    results
}

fn main() {
    let v = get_results(&[1, -6, 7]);
    let serialized = serde_json::to_string(&v).expect("Can't serialize");
    println!("serialized: {}", serialized);
    let deserialized: Vec<AgeOrError> =
        serde_json::from_str(&serialized).expect("Can't deserialize");
    println!("deserialized: {:?}", deserialized);
}

Note that in deserialization we can't reuse the automatically generated deserializers because:

  1. deserialization is kind of streaming the fields to us, we can't peek into the stringified JSON representation and guess what it is;
  2. we don't have access to the serde::de::Visitor implementations that Serde generates.

Also I did a shortcut and panicked on errors. In production code you'd want to return the proper Serde errors instead.


Another solution would be to make a merged structure with all fields optional, like this:

#[macro_use]
extern crate serde_derive; // 1.0.70
extern crate serde; // 1.0.70
extern crate serde_json; // 1.0.24

#[derive(Debug)]
pub struct MyError {
    error: String,
}

#[derive(Debug)]
pub struct MyAge {
    age: i32,
    name: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MyAgeOrError {
    #[serde(skip_serializing_if = "Option::is_none")]
    age: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    error: Option<String>,
}

impl MyAgeOrError {
    fn from_age(age: MyAge) -> MyAgeOrError {
        MyAgeOrError {
            age: Some(age.age),
            name: Some(age.name),
            error: None,
        }
    }
    fn from_error(error: MyError) -> MyAgeOrError {
        MyAgeOrError {
            age: None,
            name: None,
            error: Some(error.error),
        }
    }
}

fn get_results(ages: &[i32]) -> Vec<MyAgeOrError> {
    let mut results = Vec::with_capacity(ages.len());
    for &age in ages.iter() {
        if age < 100 && age > 0 {
            results.push(MyAgeOrError::from_age(MyAge {
                age: age,
                name: String::from("The dude"),
            }));
        } else {
            results.push(MyAgeOrError::from_error(MyError {
                error: format!("{} is invalid age", age),
            }));
        }
    }
    results
}

fn main() {
    let v = get_results(&[1, -6, 7]);
    let serialized = serde_json::to_string(&v).expect("Can't serialize");
    println!("serialized: {}", serialized);
    let deserialized: Vec<MyAgeOrError> =
        serde_json::from_str(&serialized).expect("Can't deserialize");
    println!("deserialized: {:?}", deserialized);
}

I'd vouch for this one because it allows the Rust structure (e.g. MyAgeOrError) to match the layout of your JSON. That way the JSON layout becomes documented in the Rust code.

P.S. Lately I tend to delay the decoding of optional or dynamically typed JSON parts with the help of RawValue. It's tricky to serialize them though, because RawValue is a borrow. For instance, and to help with serialization, one can intern a RawValue, promoting it to the 'static lifetime:

use serde_json::value::{RawValue as RawJson};

fn intern_raw_json(raw_json: Box<RawJson>) -> &'static RawJson {
    use parking_lot::Mutex;
    use std::mem::transmute;

    static BUF: Mutex<Vec<Pin<Box<RawJson>>>> = Mutex::new(Vec::new());

    let buf = BUF.lock();
    let raw_json: Pin<Box<RawJson>> = raw_json.into();
    let pt: &'static RawJson = {
        let pt: &RawJson = &*raw_json;
        transmute(pt)
    };
    buf.push(raw_json);
    pt
}

If performance is not an issue, then one can deserialize the dynamic parts into the Value.
Similarly, if using Value is an option, then custom deserialization can be simplified by implementing TryFrom<Value>.


Serde supports internally tagged and untagged enums as of version 0.9.6.

The following code shows an example of how this could be done by using an enum with the attribute #[serde(untagged)].

#[macro_use]
extern crate serde_derive; // 1.0.70
extern crate serde_json; // 1.0.24

#[derive(Serialize, Deserialize, Debug)]
pub struct MyError {
    error: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MyAge {
    age: i32,
    name: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum AgeOrError {
    Age(MyAge),
    Error(MyError),
}

fn get_results(ages: Vec<i32>) -> Vec<AgeOrError> {
    let mut results = Vec::with_capacity(ages.len());
    for age in ages {
        if age < 100 && age > 0 {
            results.push(AgeOrError::Age(MyAge {
                age: age,
                name: String::from("The dude"),
            }));
        } else {
            results.push(AgeOrError::Error(MyError {
                error: format!("{} is invalid age", age),
            }));
        }
    }
    results
}

fn main() {
    let results = get_results(vec![1, -6, 7]);
    let json = serde_json::to_string(&results).unwrap();
    println!("{}", json);
}

The above code outputs the following JSON:

[{"age":1,"name":"The dude"},{"error":"-6 is invalid age"},{"age":7,"name":"The dude"}]

More information on Serde's enum representation can be found in the overview.

Tags:

Json

Rust

Serde