user3384741 user3384741 - 6 months ago 72
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

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.