How can I handle duplicate fields when specifying multiple aliases using serde?

#[derive(Deserialize)]
struct MyStruct {
    #[serde(alias = "one", alias = "two" )]
    field_a: Option<String>,
    ...
}

I have something like this, but sometimes the JSON I am receiving has both fields one and two. How can I tell it to just choose the first one?

I found this recommended in several places but that appear to only work for fields which are maps, and can't be used for the entire struct.

The safer approach is to just to define a struct that accepts both and then add either an Into and TryInto to convert to your desired struct layout

That sounds good. Thanks!

While I was trying this, it seemed like not a very clean solution, at least for me. If it were a relatively small struct that would be one thing, but my real struct has about 50 fields, one or two of which are pretty large in size (possibly 100s of MB).

The first issue was I couldn't find a clean way of moving all fields with like names from StructA to StructB. Then I was also thinking those two large fields would probably have their contents moved around in memory if I'm just doing:

StructB {
   big_field: struct_a.big_field
}

right?

The large fields should be heap allocated then to not exceed any stack limits when using the struct. I think if you put the large fields each in a Box<...> I think rust will handle that for you. If you do that then moving the data to the new struct will effectively just be copying the pointer. That said, I’ve never used serde with a Box field so maybe there is a limitation I’m not aware of.

Another approach to dealing with the two fields may just be to define a helper function that returns the desired result from the two field. And maybe a setter function that sets the fields as well.

Never used serde myself before but I read the docs for an hour or so and tried to find a reasonable approach. Here’s what I came up with, hope you might find it useful:

#[derive(Deserialize)]
struct MyStructFieldA {
    one: Option<String>,
    two: Option<String>,
}
fn deserialize_field_a<'d, D: Deserializer<'d>>(d: D) -> Result<Option<String>, D::Error> {
    let MyStructFieldA { one, two } = MyStructFieldA::deserialize(d)?;
    Ok(one.or(two))
}
#[derive(Deserialize, Debug)]
struct MyStruct {
    #[serde(deserialize_with = "deserialize_field_a", flatten)]
    field_a: Option<String>,
    some: Option<i32>,
    more: Option<i32>,
    stuff: Option<i32>,
    that: Option<i32>,
    you: Option<i32>,
    dont: Option<i32>,
    want: Option<i32>,
    to: Option<i32>,
    repeat: Option<i32>,
}

(playground)


Edit: To avoid pointless extra allocation

#[derive(Deserialize)]
struct MyStructFieldA<'a> {
    one: Option<&'a str>,
    two: Option<&'a str>,
}
fn deserialize_field_a<'d, D: Deserializer<'d>>(d: D) -> Result<Option<String>, D::Error> {
    let MyStructFieldA { one, two } = MyStructFieldA::deserialize(d)?;
    Ok(one.or(two).map(Into::into))
}

(updated playground)

2 Likes

That should work. Clever solution

That's genius! Thank you so much!

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.