serde json десериализует различные объекты в массиве - PullRequest
1 голос
/ 14 июня 2019

Построение из моего вопроса ранее, теперь мне нужно десериализовать массив различных объектов. Существует существующий вопрос именно для этой ситуации, но это не для serde.

use serde::{Deserialize, Deserializer};
use serde_json;

#[derive(Debug)]
struct Duration {
    secs: u64,
    nanos: u32,
}

#[derive(Deserialize)]
struct Raw<'a> {
    #[serde(borrow)]
    secs: StrOrNum<'a>,
}

#[derive(Deserialize)]
#[serde(untagged)]
enum StrOrNum<'a> {
    Str(&'a str),
    Num(u64),
    Decimal(f64),
}

#[derive(Debug, Deserialize)]
struct DailyTask {
    name: String,
    duration: Duration,
}

#[derive(Debug, Deserialize)]
struct WeeklyTask {
    name: String,
    duration: Duration,
    priority: u8,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Task {
    Daily(DailyTask),
    Weekly(WeeklyTask),
}

#[derive(Debug, Deserialize)]
struct Tasks {
    tasks: Vec<Box<Task>>,
}

impl<'de> Deserialize<'de> for Duration {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let raw = Raw::deserialize(deserializer)?;

        match raw.secs {
            StrOrNum::Str(s) => {
                if s.parse::<f64>().is_ok() {
                    let mut p = s.splitn(2, ".").fuse();
                    let secs = p.next().map_or(0, |s| s.parse().unwrap_or(0));
                    let frac = p.next().map_or(0, |s| s.parse().unwrap_or(0));
                    let nanos = frac.to_string().len() as u32;
                    let secs = secs * 10_u64.pow(nanos) + frac;
                    Ok(Duration { secs, nanos })
                } else {
                    Err(serde::de::Error::custom(format!("Not a valid decimal: \"{}\"", s)))
                }
            }
            StrOrNum::Num(secs) => Ok(Duration { secs, nanos: 0 }),
            StrOrNum::Decimal(secs) => {
                Ok(Duration { secs: secs as u64, nanos: 0 })
            },
        }
    }
}

fn main() {
    let json_raw = r#"{
        "tasks": [
            {
                "name": "go to sleep",
                "duration": 1234
            },
            {
                "name": "go to work",
                "duration": "98.7"
            },
            {
                "name": "do that important thing",
                "duration": 56.78,
                "priority": 10
            }
        ]
    }"#;
    let j: Tasks = serde_json::from_str(&json_raw).unwrap();
    println!("{:?}", j);
}

площадка

Я не уверен, что я ошибаюсь. Есть ли и простое решение для этого, или мне нужно как-то реализовать кастом Deserialize для enum Task?

...