Десериализовать строку JSON или массив строк в Vec

Я пишу ящик, который взаимодействует с веб-API JSON. Одна конечная точка обычно возвращает ответы в форме { "key": ["value1", "value2"] }, но иногда есть только одно значение для ключа, и конечная точка возвращает { "key": "value" } вместо { "key": ["value"] }

Я хотел написать что-то общее для этого, что я мог бы использовать с #[serde(deserialize_with)] вот так:

#[derive(Deserialize)]
struct SomeStruct {
    #[serde(deserialize_with = "deserialize_string_or_seq_string")]
    field1: Vec<SomeStringNewType>,

    #[serde(deserialize_with = "deserialize_string_or_seq_string")]
    field2: Vec<SomeTypeWithCustomDeserializeFromStr>,
}

#[derive(Deserialize)]
struct SomeStringNewType(String);

struct SomeTypeWithCustomDeserializeFromStr(String);
impl ::serde::de::Deserialize for SomeTypeWithCustomDeserializeFromStr {
    // Some custom implementation here
}

Как я могу написать deserialize_string_or_seq_string чтобы быть в состоянии сделать это?

5 ответов

Решение

Это решение работает для Serde 1.0.

Способ, который я нашел, также потребовал, чтобы я написал собственный десериализатор, потому что мне нужен был тот, который будет вызывать visitor.visit_newtype_struct попытаться десериализовать новые типы, и, кажется, нет никаких встроенных в serde, которые делают это. (Я ожидал что-то вроде ValueDeserializer серия типов.)

Автономный пример ниже. SomeStruct правильно десериализован для обоих входов: один, где значения - это массивы строк JSON, а другой - просто строки.

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

fn main() {
    #[derive(Debug, Deserialize)]
    struct SomeStringNewType(String);

    #[derive(Debug)]
    struct SomeTypeWithCustomDeserializeFromStr(String);
    impl<'de> ::serde::Deserialize<'de> for SomeTypeWithCustomDeserializeFromStr {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: ::serde::Deserializer<'de> {
            struct Visitor;

            impl<'de> ::serde::de::Visitor<'de> for Visitor {
                type Value = SomeTypeWithCustomDeserializeFromStr;

                fn expecting(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                    write!(f, "a string")
                }

                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: ::serde::de::Error {
                    Ok(SomeTypeWithCustomDeserializeFromStr(v.to_string() + " custom"))
                }
            }

            deserializer.deserialize_any(Visitor)
        }
    }

    #[derive(Debug, Deserialize)]
    struct SomeStruct {
        #[serde(deserialize_with = "deserialize_string_or_seq_string")]
        field1: Vec<SomeStringNewType>,

        #[serde(deserialize_with = "deserialize_string_or_seq_string")]
        field2: Vec<SomeTypeWithCustomDeserializeFromStr>,
    }

    let x: SomeStruct = ::serde_json::from_str(r#"{ "field1": ["a"], "field2": ["b"] }"#).unwrap();
    println!("{:?}", x);
    assert_eq!(x.field1[0].0, "a");
    assert_eq!(x.field2[0].0, "b custom");

    let x: SomeStruct = ::serde_json::from_str(r#"{ "field1": "c", "field2": "d" }"#).unwrap();
    println!("{:?}", x);
    assert_eq!(x.field1[0].0, "c");
    assert_eq!(x.field2[0].0, "d custom");
}

/// Deserializes a string or a sequence of strings into a vector of the target type.
pub fn deserialize_string_or_seq_string<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
    where T: ::serde::Deserialize<'de>, D: ::serde::Deserializer<'de> {

    struct Visitor<T>(::std::marker::PhantomData<T>);

    impl<'de, T> ::serde::de::Visitor<'de> for Visitor<T>
        where T: ::serde::Deserialize<'de> {

        type Value = Vec<T>;

        fn expecting(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
            write!(f, "a string or sequence of strings")
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where E: ::serde::de::Error {

            let value = {
                // Try parsing as a newtype
                let deserializer = StringNewTypeStructDeserializer(v, ::std::marker::PhantomData);
                ::serde::Deserialize::deserialize(deserializer)
            }.or_else(|_: E| {
                // Try parsing as a str
                let deserializer = ::serde::de::IntoDeserializer::into_deserializer(v);
                ::serde::Deserialize::deserialize(deserializer)
            })?;
            Ok(vec![value])
        }

        fn visit_seq<A>(self, visitor: A) -> Result<Self::Value, A::Error>
            where A: ::serde::de::SeqAccess<'de> {

            ::serde::Deserialize::deserialize(::serde::de::value::SeqAccessDeserializer::new(visitor))
        }
    }

    deserializer.deserialize_any(Visitor(::std::marker::PhantomData))
}

// Tries to deserialize the given string as a newtype
struct StringNewTypeStructDeserializer<'a, E>(&'a str, ::std::marker::PhantomData<E>);

impl<'de, 'a, E> ::serde::Deserializer<'de> for StringNewTypeStructDeserializer<'a, E> where E: ::serde::de::Error {
    type Error = E;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error> where V: ::serde::de::Visitor<'de> {
        visitor.visit_newtype_struct(self)
    }

    fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error> where V: ::serde::de::Visitor<'de> {
        // Called by newtype visitor
        visitor.visit_str(self.0)
    }

    forward_to_deserialize_any! {
        bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str bytes
        byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct map
        struct enum identifier ignored_any
    }
}

В случае, если вы хотите десериализовать одну строку или список строк в более общий Vec<String> вместо пользовательского типа ниже приведено более простое решение для Serde 1.0:

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

use std::fmt;
use std::marker::PhantomData;

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

#[derive(Deserialize, Debug, Clone)]
pub struct Parent {
    #[serde(deserialize_with = "string_or_seq_string")]
    pub strings: Vec<String>,
}

fn main() {
    let list_of_strings: Parent = serde_json::from_str(r#"{ "strings": ["value1", "value2"] }"#).unwrap();
    println!("list of strings: {:?}", list_of_strings);
    // Prints:
    //   list of strings: Parent { strings: ["value1", "value2"] }

    let single_string: Parent = serde_json::from_str(r#"{ "strings": "value" }"#).unwrap();
    println!("single string: {:?}", single_string);
    // Prints:
    //   single string: Parent { strings: ["value"] }
}

fn string_or_seq_string<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
    where D: Deserializer<'de>
{
    struct StringOrVec(PhantomData<Vec<String>>);

    impl<'de> de::Visitor<'de> for StringOrVec {
        type Value = Vec<String>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("string or list of strings")
        }

        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where E: de::Error
        {
            Ok(vec![value.to_owned()])
        }

        fn visit_seq<S>(self, visitor: S) -> Result<Self::Value, S::Error>
            where S: de::SeqAccess<'de>
        {
            Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor))
        }
    }

    deserializer.deserialize_any(StringOrVec(PhantomData))
}

Это решение также работает в версии 0.9 Serde со следующими изменениями:

  • удалить время жизни
  • SeqAccess -> SeqVisitor
  • SeqAccessDeserializer -> SeqVisitorDeserializer
  • MapAccess -> MapVisitor
  • MapAccessDeserializer -> MapVisitorDeserializer

Я обнаружил, что этот шаблон работает для меня в аналогичной ситуации:

      use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum ParameterValue {
    Primitive(String),
    List(Vec<String>),
}

#[derive(Debug, Serialize, Deserialize)]
struct Parameter {
    name: String,
    value: ParameterValue,
}

пример примитива:

      let primitive = Parameter {
    name: String::from("theKey"),
    value: ParameterValue::Primitive(String::from("theValue")),
};
let primitive_serialized = serde_json::to_string(&primitive).unwrap();
println!("{primitive_serialized}");
let primitive_again: Parameter = serde_json::from_str(&primitive_serialized).unwrap();
println!("{primitive_again:?}");

Отпечатки:

      {"name":"theKey","value":"theValue"}
Parameter { name: "theKey", value: Primitive("theValue") }

пример массива:

      let list = Parameter {
    name: String::from("theKey"),
    value: ParameterValue::List(vec![String::from("v1"), String::from("v2")]),
};
let list_serialized = serde_json::to_string(&list).unwrap();
println!("{list_serialized}");
let list_again: Parameter = serde_json::from_str(&list_serialized).unwrap();
println!("{list_again:?}");

Отпечатки:

      {"name":"theKey","value":["v1","v2"]}
Parameter { name: "theKey", value: List(["v1", "v2"]) }

Самый короткий способ сделать это — ввести Enum и использовать Serde from и In для перевода обоих вариантов в Vec.

      #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Example {
    pub field: OneOrVec<String>,
}

Со следующими определениями:

      #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(try_from = "OneOrVecEnum<T>", into = "OneOrVecEnum<T>")]
pub struct OneOrVec<T>
where
    T: Clone,
{
    pub items: Vec<T>,
}

impl<T> From<OneOrVec<T>> for OneOrVecEnum<T>
where
    T: Clone,
{
    fn from(value: OneOrVec<T>) -> Self {
        if value.items.len() == 1 {
            let mut items = value.items;
            OneOrVecEnum::One(items.remove(0))
        } else {
            OneOrVecEnum::Multiple(value.items)
        }
    }
}

impl<T> From<OneOrVecEnum<T>> for OneOrVec<T>
where
    T: Clone,
{
    fn from(value: OneOrVecEnum<T>) -> Self {
        match value {
            OneOrVecEnum::One(item) => OneOrVec { items: vec![item] },
            OneOrVecEnum::Multiple(vec) => OneOrVec { items: vec },
        }
    }
}

Черта «Клон» необязательна, но ее полезно иметь.

Если кто-то еще борется с этим, создайте ящик serde_with в качестве аннотации только для этого: OneOrMany

Другие вопросы по тегам