use serde::{de::Error, Deserialize, Deserializer};
pub fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::{self, Visitor};
struct VecStringVisitor;
impl<'de> Visitor<'de> for VecStringVisitor {
type Value = Vec<String>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string, a string array, or a JSON array string")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut vec = Vec::new();
while let Some(elem) = seq.next_element::<String>()? {
vec.push(elem);
}
Ok(vec)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_string(v.to_string())
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: de::Error,
{
let trimmed = v.trim();
if trimmed.starts_with('[') && trimmed.ends_with(']') {
let json_str = if trimmed.starts_with('"') && trimmed.ends_with('"') {
&trimmed[1..trimmed.len() - 1]
} else {
trimmed
};
if let Ok(vec) = serde_json::from_str::<Vec<String>>(json_str) {
return Ok(vec);
}
let content = json_str
.strip_prefix('[')
.and_then(|s| s.strip_suffix(']'))
.unwrap_or(json_str);
if !content.is_empty() {
let items: Vec<String> = content
.split(',')
.map(|s| {
s.trim().trim_matches('"').trim_matches('\'').to_string()
})
.filter(|s| !s.is_empty())
.collect();
if !items.is_empty() {
return Ok(items);
}
}
}
if trimmed.contains(',') {
return Ok(trimmed
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect());
}
if !trimmed.is_empty() {
Ok(vec![trimmed.to_string()])
} else {
Ok(Vec::new())
}
}
}
deserializer.deserialize_any(VecStringVisitor)
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct TestConfig {
#[serde(default, deserialize_with = "deserialize_string_or_vec")]
pub items: Vec<String>,
}
#[test]
fn test_deserialize_json_array_string() {
let json = r#"{"items": "[\"a\",\"b\",\"c\"]"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["a", "b", "c"]);
}
#[test]
fn test_deserialize_json_array_with_spaces() {
let json = r#"{"items": "[\"a\", \"b\", \"c\"]"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["a", "b", "c"]);
}
#[test]
fn test_deserialize_comma_separated() {
let json = r#"{"items": "a,b,c"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["a", "b", "c"]);
}
#[test]
fn test_deserialize_comma_separated_with_spaces() {
let json = r#"{"items": "a, b, c"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["a", "b", "c"]);
}
#[test]
fn test_deserialize_single_string() {
let json = r#"{"items": "single-item"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["single-item"]);
}
#[test]
fn test_deserialize_direct_array() {
let json = r#"{"items": ["x", "y", "z"]}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["x", "y", "z"]);
}
#[test]
fn test_deserialize_empty_string() {
let json = r#"{"items": ""}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, Vec::<String>::new());
}
#[test]
fn test_deserialize_empty_array() {
let json = r#"{"items": []}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, Vec::<String>::new());
}
#[test]
fn test_deserialize_array_without_quotes() {
let json = r#"{"items": "[ms-identity]"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["ms-identity"]);
}
#[test]
fn test_deserialize_array_without_quotes_multiple() {
let json = r#"{"items": "[ms-identity,ms-auth,ms-notify]"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["ms-identity", "ms-auth", "ms-notify"]);
}
#[test]
fn test_deserialize_array_without_quotes_with_spaces() {
let json = r#"{"items": "[ms-identity, ms-auth]"}"#;
let config: TestConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.items, vec!["ms-identity", "ms-auth"]);
}
}
pub fn deserialize_u32_from_str_or_num<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrNum {
String(String),
Num(u32),
}
match StringOrNum::deserialize(deserializer)? {
StringOrNum::String(s) => s.parse().map_err(Error::custom),
StringOrNum::Num(n) => Ok(n),
}
}
pub fn deserialize_option_u32_from_str_or_num<'de, D>(
deserializer: D,
) -> Result<Option<u32>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrNum {
String(String),
Num(u32),
Null,
}
match StringOrNum::deserialize(deserializer)? {
StringOrNum::String(s) => {
if s.is_empty() {
Ok(None)
} else {
s.parse().map(Some).map_err(Error::custom)
}
}
StringOrNum::Num(n) => {
if n == 0 {
Ok(None)
} else {
Ok(Some(n))
}
}
StringOrNum::Null => Ok(None),
}
}