jonasbb / serde_with

This crate provides custom de/serialization helpers to use in combination with serde's `with`-annotation and with the improved `serde_as`-annotation.
https://docs.rs/serde_with
Apache License 2.0
636 stars 67 forks source link

`JsonString` and deserializing a JSON string containing an array of objects #757

Closed dcormier closed 2 months ago

dcormier commented 2 months ago

Am I missing something, or does using JsonString to deserialize a JSON string containing an array of objects not work?

use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_with::serde_as;

#[test]
fn deserialize_json_array_str() {
    let value = json!([Inner { name: "One".into() }, Inner { name: "Two".into() }]);
    let value = json!({"value":value.to_string()});

    println!("Value being deserialized: {value:#?}");
    println!("JSON representation: {value}");

    let outer: Outer = serde_json::from_value(value).expect("Failed to deserialize");
    assert_eq!(
        vec![Inner { name: "One".into() }, Inner { name: "Two".into() }],
        outer.value
    );
}

#[derive(Debug, Deserialize)]
#[serde_as]
struct Outer {
    // These other two attempts made no apparent difference.
    // #[serde_as(as = "JsonString<Vec<Inner>>")]
    // #[serde_as(as = "JsonString<Vec<_>>")]
    #[serde_as(as = "JsonString")]
    pub value: Vec<Inner>,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Inner {
    pub name: String,
}

The output is:


Value being deserialized: Object {
    "value": String("[{\"name\":\"One\"},{\"name\":\"Two\"}]"),
}
JSON representation: {"value":"[{\"name\":\"One\"},{\"name\":\"Two\"}]"}
thread 'js::deserialize_json_array_str' panicked at src/main.rs:469:58:
Failed to deserialize: Error("invalid type: string \"[{\\\"name\\\":\\\"One\\\"},{\\\"name\\\":\\\"Two\\\"}]\", expected a sequence", line: 0, column: 0)
jonasbb commented 2 months ago

You need to switch the order of the macro annotations around. serde_as must always come before the derive. The reason is that the macro order is significant in Rust as the macros are expanded top down. You need to place serde_as first as it needs to run before the derive generates all its code, as otherwise the derive will not know about the changed behavior. That should fix everything. You now just need to import serde_with::json::JsonString.

#[serde_as]
#[derive(Debug, Deserialize)]