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.