Serde flatten to existence flag

Hi, I want to test if some JSON has extraneous fields or not.
I only care if there's a uid and maybe a status field.
But I want to perform a specialized aciton if the are only those two fields.

I know that I can use #[serde(flatten)] to capture those fields into a container
and then test the container using is_empty().

#[derive(Deserialize)]
struct Entry {
    uid: String,

    status: Option<Status>,

    #[serde(flatten)]
    rest: HashMap<String, json::Value>,
}

Is there a way where I can skip creating all those container entries and set a flag is there are more fields?. Something like the code below?

#[derive(Deserialize)]
struct Entry {
    uid: String,

    status: Option<Status>,

    #[serde(flatten)]
    has_more_fields: bool,
}

Thanks

Create a custom container type that only stores whether or not it is empty.

What's the definition of 'container type' to Serde?

Serde has no such attribute. You could use the #[serde(from = "FromType")] container attribute to convert a different type (with the additional fields) into your desired type with only the flag:

use serde::Deserialize;
use serde_json;

use std::collections::HashMap;

#[derive(Deserialize)]
struct RawEntry {
    uid: String,
    status: Option<u8>,
    #[serde(flatten)]
    rest: HashMap<String, serde_json::Value>,
}

#[derive(Deserialize)]
#[serde(from = "RawEntry")]
struct Entry {
    uid: String,
    status: Option<u8>,
    has_more_fields: bool,
}

impl From<RawEntry> for Entry {
    fn from(re: RawEntry) -> Self {
        Self {
            uid: re.uid,
            status: re.status,
            has_more_fields: !re.rest.is_empty(),
        }
    }
}

fn main() {
    let json = r#"{
        "uid": "uid",
        "status": null,
        "another_field": "hello"
    }"#;
    
    let entry: Entry = serde_json::from_str(json).unwrap();
    
    assert!(entry.has_more_fields);
}

Playground.

I personally like your suggested solution better though. Why not have your flag as a method on Entry and keep the additional fields around?

Not sure based on the documentation, but since proc-macros are a purely syntactic abstraction, container types shouldn't be special in any way. Your custom type will probably be treated as a Deserialize-able, and it will be passed a Deserializer on which you can call deserialize_struct() and/or deserialize_map().

Here's a minimal, working example which confirms my conjecture.

5 Likes

Hey @jofas, thanks for taking the time to reply.

It's a wondering about performance implications of making a hashmap and filling it with heap allocated Strings when I don't have to.

So it's a bit pedantic, but I would like to avoid allocating a relatively complex collection such as a HashMap just for the sake of saying 'Yes, more fields are present'.

Ah yes true, a collection in this case is something implementing Deserialize.

Thanks for the example, it looks like that's a good working solution.
I'll take a look at how serde::de::IgnoredAny is implemented, but I'm presuming no allocation occurs.

Thanks @H2CO3 :wink:

Of course not. The whole design of Serde avoids allocating by itself in most cases with the visitor pattern – unless the type to be deserialized allocates memory for itself, there won't be extra allocations resulting just form parsing the serialized representation.

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.