Rust Serde Custom Field Deserializer
Accept a space-separated string or a JSON array for one Serde field using a custom Visitor, defaults, and unknown-field rejection.
Code
use std::fmt;
use serde::de::{self, Deserializer, SeqAccess, Visitor};
use serde::{Deserialize, Serialize};
fn deserialize_origins<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
{
struct OriginsVisitor;
impl<'de> Visitor<'de> for OriginsVisitor {
type Value = Vec<String>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a space-separated string or an array of strings")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(value.split_whitespace().map(str::to_owned).collect())
}
fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut origins = Vec::new();
while let Some(origin) = sequence.next_element::<String>()? {
origins.push(origin);
}
Ok(origins)
}
}
deserializer.deserialize_any(OriginsVisitor)
}
fn default_replicas() -> u8 {
2
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
struct ServiceConfig {
name: String,
#[serde(default = "default_replicas")]
replicas: u8,
#[serde(default, deserialize_with = "deserialize_origins")]
allowed_origins: Vec<String>,
}
fn main() {
let expanded = r#"{
"name": "checkout-api",
"replicas": 4,
"allowed-origins": "https://shop.example https://admin.example"
}"#;
let config: ServiceConfig = serde_json::from_str(expanded).unwrap();
println!("{config:?}");
assert_eq!(config.allowed_origins.len(), 2);
let array_form = r#"{"name":"worker","allowed-origins":["https://a.example"]}"#;
let config: ServiceConfig = serde_json::from_str(array_form).unwrap();
println!("default replicas: {}", config.replicas);
assert_eq!(config.replicas, 2);
let unknown = r#"{"name":"api","replica":3}"#;
let error = serde_json::from_str::<ServiceConfig>(unknown).unwrap_err();
println!("rejected unknown field: {error}");
}Report a problem with this exampleOpens a pre-filled issue on GitHub.