Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to define a tag field with Serde?

Tags:

rust

serde

I want something like this:

#[derive(Debug, Serialize, Deserialize)]
struct MyStruct {
    field1: String,
    field2: Option<u64>,
    #[serde(tag(value = "tag_value"))]
    tag: ()
}

#[serde(tag(value = "tag_value"))] is not an actual Serde-provided attribute, it's here just to express an idea. I'm aware that I can do all the serialization by myself, use remote, (de)serialize_with, etc., but these require a lot of boilerplate code.

The idea is that a tag field must exist, MyStruct serialized to JSON should be:

{
    "field1": "foo",
    "field2": 42,
    "tag": "tag_value"
}

Deserialization must fail if either "tag" field is missing or maps to a different value from "tag_value".

like image 522
L117 Avatar asked Sep 01 '25 10:09

L117


2 Answers

For the limited case of just one tag, you can use #[serde(tag)] on the struct itself:

use serde::Serialize; // 1.0.114
use serde_json; // 1.0.56

#[derive(Debug, Serialize)]
#[serde(tag = "tag", rename = "tag_value")]
struct MyStruct {
    field1: String,
    field2: Option<u64>,
}

fn main() {
    let s = MyStruct {
        field1: "hello".into(),
        field2: None,
    };
    println!("{}", serde_json::to_string(&s).unwrap());
}
{"tag":"tag_value","field1":"hello","field2":null}
like image 137
Shepmaster Avatar answered Sep 04 '25 18:09

Shepmaster


Use a single variant enumeration:

use serde; // 1.0.104
use serde_json; // 1.0.48

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
enum Tag {
    TagValue,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct MyStruct {
    field1: String,
    field2: Option<u64>,
    tag: Tag,
}

fn main() {
    let s = MyStruct {
        field1: "foo".to_string(),
        field2: Some(42),
        tag: Tag::TagValue,
    };

    // The tag is included when serializing
    println!("{:?}", serde_json::to_string(&s));

    // Tag is required when deserializing
    println!(
        "{:?}",
        serde_json::from_str::<MyStruct>(
            "{\"field1\":\"foo\",\"field2\":42,\"tag\":\"tag_value\"}"
        )
    );
    println!(
        "{:?}",
        serde_json::from_str::<MyStruct>("{\"field1\":\"foo\",\"field2\":42}")
    );

    // A bad tag fails
    println!(
        "{:?}",
        serde_json::from_str::<MyStruct>("{\"field1\":\"foo\",\"field2\":42,\"tag\":\"oops\"}")
    );
}

This prints

Ok("{\"field1\":\"foo\",\"field2\":42,\"tag\":\"tag_value\"}")
Ok(MyStruct { field1: "foo", field2: Some(42), tag: TagValue })
Err(Error("missing field `tag`", line: 1, column: 28))
Err(Error("unknown variant `oops`, expected `tag_value`", line: 1, column: 40))

(Permalink to the playground)

like image 26
mcarton Avatar answered Sep 04 '25 17:09

mcarton