Embed json string in Serde serializer

I want to embed the following json string in a struct:

let x = r#"{"name":"ABC","age":123}"#;

struct Wrapper {
    data: &'static str
}

let wrapper = Wrapper { data: x };

I want the serialized wrapper instance to look like this:

{"data":{"name":"ABC","age":123}}

I tried implementing custom Serialize using serializer.serialize_str, but it seems all the double quotes are escaped and the serialized version looks like the following:

{"data":"{\"name\":\"ABC\",\"age\":123}"}

No surprise โ€“ the JSON you want has the data as a proper JSON sub-object, not as a string. If you want to embed literal structured json, use the json!() macro from serde_json. That creates a dynamic serde_json::Value that serializes properly.

I think what you are misunderstanding is that the various serialize_xxx methods on Serializer write the representation of the passed value in the shape specific to whatever serialization format you are using. Serializer::serialize_str() doesn't just dump raw string as-is โ€“ it writes a string literal quoted and escaped (or not) according to the spec of the concrete format. That is the job of a Serializer.

The Serialize impl on your own data structure to-be-serialized doesn't dictate what literal contents will be emitted, it dictates what value the serializer should emit, according to its own syntactic form. The whole point of Serde is to separate the data from the format.

Thanks for the reply @H2CO3!
Actually, I need the struct Wrapper to impl serde::Serialize. AFAIK, json!() returns enum serde_json::Value. Is there a way to have the struct Wrapper implement serde::Serialize?

serde_json::Value is already Serialize, that's exactly what I'm talking about. (So if you put it in your own struct, you can just derive the Serialize impl and it will do the right thing.)

Sorry, but I'm totally confused on how can I put it in my struct.

use serde::Serialize;
use serde_json;

struct Wrapper {
    pub data: &'static str
}

let wrapper = Wrapper { data: r#"{"name":"ABC","age":123}"# };

fn print_serialize(v: Serialize) {
  println!("{}", serde_json::to_string(&v).unwrap());
}

// What I need:
println_serialize(wrapper);

Can you please update this example to use json!()?

use serde_json::json;

struct Wrapper {
    data: serde_json::Value,
}

fn main() {
    let w = Wrapper {
        data: json!({"age": 123}),
    };
}

In json!({"age": 123}), {"age": 123} is not a json string. What I need to do is just, as you mentioned, dumping my string, which I'm sure it's valid json string, in the serialized output.

Indeed it's not a string literal, but you can try my code and see for yourself that it does what you want. If you have a literal JSON structure, there is no reason to convert it to a string, just to convert it right back to JSON.

But if you still want to do that for some reason, you can parse the string literal into a serde_json::Value.

Of course I tried your example, and json!() also escapes double quotes and the resulting json is:

{"data":"{\"name\":\"ABC\",\"age\":123}"}

Also, I see deserialize-then-serializing a json string as a hack. Maybe it was better that serde serializer exposes a method that allows simply writing directly to its internal writer, instead of making such a simple task complex and almost impossible to achieve.

After all, thank you @H2CO3 for your help!

serde_json offers the RawValue type which lets you emit verbatim JSON

use serde_json::value::RawValue;

fn main() {
    let x = r#"{"name":"ABC","age":123}"#;
    
    #[derive(serde::Serialize)]
    struct Wrapper<'a> {
        #[serde(borrow)]
        data: &'a RawValue
    }
    
    let wrapper = Wrapper { data: serde_json::from_str(&x).unwrap() };
    
    let out = serde_json::to_string(&wrapper).unwrap();
    println!("{out}");
}

Playground

2 Likes

Thank you @jonasbb. It seems to be the real solution.

Can you also point me to the documentation for #[serde(borrow)] attribute please?

Edit: I found the doc for all serde field attributes here: Field attributes ยท Serde.

borrow has no effect on serialization afaik, it's just for deserialization

It doesn't. I have no idea what you did, but my example works as intended.