Преобразуйте два типа в один с помощью Serde

Я пишу для программы, которая подключается к веб-службе, которая отправляет обратно JSON.

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

Структуры Ржавчины

#[derive(Clone, Debug, Deserialize)]
struct WebResponse {
    foo: Vec<Foo>,
}

#[derive(Clone, Debug, Deserialize)]
struct Foo {
    points: Points,
}

#[derive(Clone, Debug, Deserialize)]
struct Points {
    x: u64,
    y: u64,
    name: String,
}

Пример JSON

{
    "foo":[
        {
            "points":{
                "x":"",
                "y":"",
                "name":""
            }
        },
        {
            "points":{
                "x":78,
                "y":92,
                "name":"bar"
            }
        }
    ]
}

person XAMPPRocky    schedule 16.06.2016    source источник
comment
Что должно произойти, если вы получите пустую строку вместо u64? Вы хотите установить в поле значение 0? кому None? что-то другое?   -  person Francis Gagné    schedule 17.06.2016
comment
@ FrancisGagné Думаю, это будет 0.   -  person XAMPPRocky    schedule 17.06.2016


Ответы (2)


Serde поддерживает интересный набор атрибутов, которые можно использовать для настройки сериализации или десериализации для типа, пока используя производную реализацию по большей части.

В вашем случае вам необходимо иметь возможность декодировать поле, которое может быть указано как один из нескольких типов, и вам не нужна информация из других полей, чтобы решить, как декодировать проблемные поля. Аннотация #[serde(deserialize_with="$path")] хорошо подходит для решения вашей проблемы.

Нам нужно определить функцию, которая будет декодировать либо пустую строку, либо целочисленное значение в u64. Мы можем использовать одну и ту же функцию для обоих полей, поскольку нам нужно одинаковое поведение. Эта функция будет использовать пользовательский Visitor, чтобы иметь возможность для обработки как строк, так и целых чисел. Это немного длинновато, но заставляет вас ценить всю работу, которую Серде делает для вас!

extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use serde::Deserializer;
use serde::de::{self, Unexpected};
use std::fmt;

#[derive(Clone, Debug, Deserialize)]
struct WebResponse {
    foo: Vec<Foo>,
}

#[derive(Clone, Debug, Deserialize)]
struct Foo {
    points: Points,
}

#[derive(Clone, Debug, Deserialize)]
struct Points {
    #[serde(deserialize_with = "deserialize_u64_or_empty_string")]
    x: u64,
    #[serde(deserialize_with = "deserialize_u64_or_empty_string")]
    y: u64,
    name: String,
}

struct DeserializeU64OrEmptyStringVisitor;

impl<'de> de::Visitor<'de> for DeserializeU64OrEmptyStringVisitor {
    type Value = u64;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("an integer or a string")
    }

    fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(v)
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        if v == "" {
            Ok(0)
        } else {
            Err(E::invalid_value(Unexpected::Str(v), &self))
        }
    }
}

fn deserialize_u64_or_empty_string<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_any(DeserializeU64OrEmptyStringVisitor)
}

fn main() {
    let value = serde_json::from_str::<WebResponse>(
        r#"{
        "foo": [
            {
                "points": {
                    "x": "",
                    "y": "",
                    "name": ""
                }
            },
            {
                "points": {
                    "x": 78,
                    "y": 92,
                    "name": "bar"
                }
            }
        ]
    }"#,
    );
    println!("{:?}", value);
}

Cargo.toml:

[dependencies]
serde = "1.0.15"
serde_json = "1.0.4"
serde_derive = "1.0.15"
person Francis Gagné    schedule 17.06.2016

В str_or_u64 мы используем немаркированное перечисление для представления строка или число. Затем мы можем десериализовать поле в это перечисление и преобразовать его в число.

Мы аннотируем два поля в Points, используя deserialize_with, чтобы указать ему использовать наше специальное преобразование:

use serde::{Deserialize, Deserializer}; // 1.0.124
use serde_json; // 1.0.64

#[derive(Debug, Deserialize)]
struct WebResponse {
    foo: Vec<Foo>,
}

#[derive(Debug, Deserialize)]
struct Foo {
    points: Points,
}

#[derive(Debug, Deserialize)]
struct Points {
    #[serde(deserialize_with = "str_or_u64")]
    x: u64,
    #[serde(deserialize_with = "str_or_u64")]
    y: u64,
    name: String,
}

fn str_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum StrOrU64<'a> {
        Str(&'a str),
        U64(u64),
    }

    Ok(match StrOrU64::deserialize(deserializer)? {
        StrOrU64::Str(v) => v.parse().unwrap_or(0), // Ignoring parsing errors
        StrOrU64::U64(v) => v,
    })
}

fn main() {
    let input = r#"{
        "foo":[
            {
                "points":{
                    "x":"",
                    "y":"",
                    "name":""
                }
            },
            {
                "points":{
                    "x":78,
                    "y":92,
                    "name":"bar"
                }
            }
        ]
    }"#;

    dbg!(serde_json::from_str::<WebResponse>(input));
}

Смотрите также:

person Shepmaster    schedule 06.04.2021