Newbie in Rust here.
I'm having some difficulties parsing a escaped json string into a Option<Vec<String>>
.
I have an api that's returning a collection of items, and one of the properties contains an array of strings, however, the array is escaped as a string. Here an example, please check the keywords property.
{
"id": "123123132",
"properties": {
"createdate": "2023-06-25T03:10:43.312Z",
"description": "Lorem Impsum ......",
"keywords": "[\"keyword1\", \"keyword2\", \"keyword3\"]"
},
"createdAt": "2023-06-25T03:10:43.312Z",
"updatedAt": "2024-04-03T14:40:20.360Z",
},
{
"id": "789789789",
"properties": {
"createdate": "2023-06-25T03:10:43.312Z",
"description": "Another description ......",
"keywords": null
},
"createdAt": "2023-06-25T03:10:43.312Z",
"updatedAt": "2024-04-03T14:40:20.360Z",
},
Sadly, I do not have any control over the API, so I was trying to parse it as a string array. Also, that property is not mandatory, so, sometimes can contain null values.
I have tried implementing the following code in Rust:
fn deserialize_keywords<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: &str = de::Deserialize::deserialize(deserializer)?;
match serde_json::from_str(s) {
Result::Ok(v) => Result::Ok(Some(v)),
Result::Err(e) => Result::Ok(None)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ItemProperties {
id: String,
description: Option<String>,
#[serde(deserialize_with = "deserialize_keywords")]
keywords: Option<Vec<String>>,
#[serde(alias = "createdate")]
created_date: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Item {
pub id: String,
pub properties: ItemProperties,
#[serde(alias = "createdAt")]
pub created_at: String,
#[serde(alias = "updatedAt")]
pub updated_at: String,
}
#[derive(Deserialize)]
struct TypeResponse {
results: Vec<Item>,
total: usize,
}
let response = self.client.post("https://api.com/items")
.send()
.await?
.json::<TypeResponse>()
.await?;
However, when I try that I have the following error:
Error: error decoding response body: invalid type: null, expected a borrowed string at line 1 column 349
Caused by:
invalid type: null, expected a borrowed string at line 1 column 349
I actually tried a few more things, but nothing has worked so far. Any idea how can I achieve it?
As BalllpointBen already mentioned, you cannot deserialize a JSON string with escape sequences into &str
so you have to replace it with String
and since it may not be there (null
is possible) you'll have to deserialize into an Option<String>
:
use serde::{de, Deserialize};
fn deserialize_keywords<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let Some(keywords_string): Option<String> = de::Deserialize::deserialize(deserializer)? else {
return Ok(vec![]);
};
let Ok(keywords) = serde_json::from_str(&keywords_string) else {
return Ok(vec![]);
};
Ok(keywords)
}
// here follows a more minimal example I used to test.
const JSON: &str =
r#"[{ "keywords": "[\"keyword1\", \"keyword2\", \"keyword3\"]"}, {"keywords": null}]"#;
fn main() {
let foos: Vec<Foo> = serde_json::from_str(JSON).unwrap();
dbg!(foos);
}
#[derive(Debug, Deserialize)]
struct Foo {
#[serde(deserialize_with = "deserialize_keywords")]
keywords: Vec<String>,
}
I've additionally removed the ambiguous representation of no keywords (Some(vec![])
and None
) since that's unlikely what you want.