user3384741 user3384741 - 1 year ago 357
JSON Question

Rust serde JSON array with different objects/errors?

I want to create an array with error messages as well as proper objects.

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

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

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


I use serde to serialize the JSON. When I pass in the Vec
[1,-6,7]
I want an array that serializes in serde to the JSON:

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


How do I do that? Knowing how to deserialize such an array would be nice as well.

Answer Source

Well, here's one way of doing that:

#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

extern crate serde;
extern crate serde_json;

#[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: &mut S) -> Result<(), 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 serde::Deserialize for AgeOrErrorField {
    fn deserialize<D>(deserializer: &mut D) -> Result<AgeOrErrorField, D::Error>
        where D: serde::Deserializer
    {
        struct AgeOrErrorFieldVisitor;
        impl serde::de::Visitor for AgeOrErrorFieldVisitor {
            type Value = AgeOrErrorField;
            fn visit_str<E>(&mut self, value: &str) -> Result<AgeOrErrorField, E>
                where E: serde::Error
            {
                Ok(match value {
                    "age" => AgeOrErrorField::Age,
                    "name" => AgeOrErrorField::Name,
                    "error" => AgeOrErrorField::Error,
                    _ => panic!("Unexpected field name: {}", value),
                })
            }
        }
        deserializer.deserialize(AgeOrErrorFieldVisitor)
    }
}

impl serde::Deserialize for AgeOrError {
    fn deserialize<D>(deserializer: &mut D) -> Result<AgeOrError, D::Error>
        where D: serde::Deserializer
    {
        deserializer.deserialize_map(AgeOrErrorVisitor)
    }
}
struct AgeOrErrorVisitor;
impl serde::de::Visitor for AgeOrErrorVisitor {
    type Value = AgeOrError;
    fn visit_map<V>(&mut self, mut visitor: V) -> Result<AgeOrError, V::Error>
        where V: serde::de::MapVisitor
    {
        let mut age: Option<i32> = None;
        let mut name: Option<String> = None;
        let mut error: Option<String> = None;
        loop {
            match try!(visitor.visit_key()) {
                Some(AgeOrErrorField::Age) => age = try!(visitor.visit_value()),
                Some(AgeOrErrorField::Name) => name = try!(visitor.visit_value()),
                Some(AgeOrErrorField::Error) => error = try!(visitor.visit_value()),
                None => break,
            }
        }
        try!(visitor.end());
        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
}

pub 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:
a) deserialization is kind of streaming the fields to us, we can't peek into the stringified JSON representation and guess what it is;
b) 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:

#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

extern crate serde;
extern crate serde_json;

#[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
}

pub 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 docummented in the Rust code.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download