Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.8k views
in Technique[技术] by (71.8m points)

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

I want to use Serde to create an array with error messages as well as proper objects:

extern crate serde; // 1.0.70
#[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,
}

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
}

When I pass in the Vec [1, -6, 7] I want to serialize 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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>.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...