I'd like to convert BTreeMap value, when String, to Vec<String>

use serde::de::{Deserialize, Deserializer, MapAccess, Visitor};
use serde_json;
use std::{collections::BTreeMap, fmt};

#[derive(Clone, Debug, Default, PartialEq)]
pub struct MetadataValue(pub BTreeMap<String, Vec<String>>);

impl<'de> Deserialize<'de> for MetadataValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct MetadataValueVisitor;

        impl<'de> Visitor<'de> for MetadataValueVisitor {
            type Value = MetadataValue;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("tuple struct MetadataValue")
            }

            fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
            where
                M: MapAccess<'de>,
            {
                let mut map = MetadataValue::default();

                while let Some((k, v)) = access.next_entry().or_else(|_| {
                    if let Some((k, v)) = access.next_entry::<String, String>()? {
                        return Ok(Some((k, [v].to_vec())));
                    }
                    Ok(None)
                })? {
                    map.0.insert(k, v);
                }

                Ok(map)
            }
        }

        deserializer.deserialize_map(MetadataValueVisitor)
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mv: MetadataValue = serde_json::from_value(serde_json::json!({
        "a": "b"
    }))?;

    assert_eq!(mv.0.get("a"), Some(&vec!["b".to_string()]));

    Ok(())
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 2.99s
     Running `target/debug/playground`
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `None`,
 right: `Some(["b"])`', src/main.rs:50:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

If next_entry() fails to parse the value, then the deserializer has already succeeded to parse the key, and so it has to skip the value too in order to avoid being left in an inconsistent state between the key and the value. You can't just backtrack because parsing is expected to be streaming in general, and so the next call to next_entry() will see nothing.

I suggest you delegate the hard part of the work to a #[serde(untagged)] enum instead, which contains either a single string or a vector of strings (Playground):

#[derive(Deserialize)]
#[serde(untagged)]
enum Value {
    One(String),
    Many(Vec<String>),
}

…

fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
    M: MapAccess<'de>,
{
    let mut map = MetadataValue::default();

    while let Some((k, v)) = access.next_entry::<_, Value>()? {
        map.0.insert(k, v.into());
    }

    Ok(map)
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.