Skip to content

deserialize w/ a tag + catch-all (flatten) surprisingly duplicates tag #3028

@michaelkirk

Description

@michaelkirk

You can use flatten to capture additional fields.
You can use tag on a struct to add a field during serialization. This tag is also used for discriminating enums during deserialization.

But when you use them together, it seems as though the tag gets redundantly processed into the flattened field.

My actual use case is georust/geojson#269, but here's a simpler version:

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

use std::collections::HashMap;

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
struct Foo {
    name: String,
    #[serde(flatten)]
    other: HashMap<String, String>
}

fn main() {
    // case 1
    let input = json!({
        "type": "Foo",
        "name": "Frank",
        "bar": "bar_value"
    }).to_string();
    let foo: Foo = serde_json::from_str(&input).unwrap();
    // Prints `type` as member of `other`:
    //       GOT> Foo { name: "Frank", other: {"type": "Foo", "bar": "bar_value"} }
    // But I'm expecting no explicit type field.
    // EXPECTING > Foo { name: "Frank", other: {bar": "bar_value"} }
    println!("deserialized: {foo:?}");
    
    // case 2
    let wrong_input = json!({
        "type": "WrongType",
        "name": "Frank",
        "bar": "bar_value"
    }).to_string();
    let foo_wrong: Foo = serde_json::from_str(&wrong_input).unwrap();
    // Successfully parsed! Prints `type` as member of `other`:
    //       GOT> Foo { name: "Frank", other: {"type": "WrongType", "bar": "bar_value"} }
    // But I'm expecting `serde_json::from_str` to fail, since `type` doesn't match the expected tag value ("Foo").
    println!("deserialized with wrong type: {foo_wrong:?}");

    // case 3
    let serialized = serde_json::to_string(&foo).unwrap();
    // Prints TWO type fields:
    //       GOT> {"type":"Foo","name":"Frank","type":"Foo","bar":"bar_value"}
    // But I'm expecting only ONE type field:
    // EXPECTING> {"type":"Foo","name":"Frank","bar":"bar_value"}
    println!("serialized: {serialized}");
}

This seems similar to #1786, but my example is a little different because it includes a flattened member.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions