Use serde to deserialize a Vec<StringOrVecString> into a Vec<String>

Hello, I would like to use serde to deserialize an array of (string or array of string) to an array of string.

Actually it an { items: { kind: "a" | "b", name: string | string } } to { items: { kind: "a" | "b", name: string } }, sorry for the TS notation, the playground should be clearer.

But I can't figure it out :confused:

use serde::{Deserialize, Serialize};



fn main() {
    first_try();
    second_try();
    third_try();
}

fn first_try() {
    #[derive(Debug, Clone, Deserialize, Serialize)]
    struct Data {
        items: Vec<Item>,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize)]
    struct Item {
        kind: Kind,
        name: String
    }
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
    #[serde(rename_all = "snake_case")]
    enum Kind {
        A,
        B,
    }
    
    let data = r#"
    {
        "items": [
            {
                "kind":"a",
                "name": "a-1"
            },
            {
                "kind":"a",
                "name": "a-2"
            },
            {
                "kind":"b",
                "name": "b-1"
            }
        ]
    }"#;
    let items = serde_json::from_str::<Data>(data).expect("Cannot deserialize config");
    dbg!(items); // this works
    // But I would like to be able to set multiple item of the same kind in one object
    // like in `second_try`
}

fn second_try() {
    let data = r#"
    {
        "items": [
            {
                "kind":"a",
                "name": ["a-1", "a-2"]
            },
            {
                "kind":"b",
                "name": "b-1"
            }
        ]
    }"#;
    
    #[derive(Debug, Clone, Deserialize, Serialize)]
    #[serde(untagged)]
    enum OneOrManyString {
        One(String),
        Many(Vec<String>)
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize)]
    struct Data {
        items: Vec<Item>,
    }
    
    #[derive(Debug, Clone, Deserialize, Serialize)]
    struct Item {
        kind: Kind,
        name: OneOrManyString
    }
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
    #[serde(rename_all = "snake_case")]
    enum Kind {
        A,
        B,
    }
    
    let items = serde_json::from_str::<Data>(data).expect("Cannot deserialize config");
    dbg!(items); // this works but is deserialized as
    /*
    Data {
        items: [
            Item {
                kind: A,
                name: Many(
                    [
                        "a-1",
                        "a-2",
                    ],
                ),
            },
            Item {
                kind: B,
                name: One(
                    "b-1",
                ),
            },
        ],
    }
    
    and I would like the same as in first_try:
    
    Data {
        items: [
            Item {
                kind: A,
                name: "a-1",
            },
            Item {
                kind: A,
                name: "a-2",
            },
            Item {
                kind: B,
                name: "b-1",
            },
        ],
    }
    */
}

Here is a playground link: Rust Playground

You can use an untagged enum for the name field so that the deserializer's visitor decides which variant it is.

Isn't that what I did in the second_try function? It kind of works but it is deserialized as:

    Data {
        items: [
            Item {
                kind: A,
                name: Many(
                    [
                        "a-1",
                        "a-2",
                    ],
                ),
            },
            Item {
                kind: B,
                name: One(
                    "b-1",
                ),
            },
        ],
    }

And I would like:

    Data {
        items: [
            Item {
                kind: A,
                name: "a-1",
            },
            Item {
                kind: A,
                name: "a-2",
            },
            Item {
                kind: B,
                name: "b-1",
            },
        ],
    }

Ah, yes. But the problem is that you are mistaken the debug representation for the deserialized representation.

I'm not sure what you mean.

Do you mean that the debug representation doesn't match the actual structure? Is items.items[0].name actually a String? Because it's not according to rust:

[src/main.rs:134] &items.items[0].name = Many(
    [
        "a-1",
        "a-2",
    ],

No. What I mean is that it seems that you are confused by the debug representation, while you already have what you wanted.

I don't think so. I want &items.items[0].name to always be a String (never a Vec) once it's been deserialized. Which is not the case.

Edit: Sorry, I was on the phone while walking my dog and forgot your first requirement.

Let's start from the beginning: You want to, during deserialization, to transform a string | string[] (in TypeScript notation) into a string.

There are two ways to solve this that come to mind. One would be to implement the deserializer yourself. You can read more about this approach here.

Another, easier solution would be to create and intermediate representation for the deserialization:

struct Item {
  kind: Kind,
  name: String
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
enum OneOrManyString {
  One(String),
  Many(Vec<String>)
}

#[derive(Debug, Clone, Deserialize, Serialize)]
struct ItemForDeserialization {
  kind: Kind,
  name: OneOrManyString
}

impl From<ItemForDeserialization> for Item {
  // Convert ItemForDeserialization into Item, so that you flatten everything into a String
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.