Serde: More compact JSON, without field names?

This is my first playing with serde so I might be asking some stupid questions here.

Given this trivial code:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Ages {
    birth: i32,
    elected: i32,
    died: i32,
}

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    ages: Ages,
}

fn main() {
    let original_person = Person {
        name: String::from("Thomas"),
        ages: Ages {
            birth: 1743,
            elected: 1801,
            died: 1826,
        },
    };
    println!("Rust original_person: {:?}", original_person);

    let json_out = serde_json::to_string(&original_person).unwrap();
    println!("Their JSON: {}", json_out);

    let deserialized_person: Person = serde_json::from_str(&json_out).unwrap();
    println!("deserialized_person: {:?}", deserialized_person);
}

I get this output:

Rust original_person: Person { name: "Thomas", ages: Ages { birth: 1743, elected: 1801, died: 1826 } }
Their JSON: {"name":"Thomas","ages":{"birth":1743,"elected":1801,"died":1826}}
deserialized_person: Person { name: "Thomas", ages: Ages { birth: 1743, elected: 1801, died: 1826 } }

Yay! It works! (I'm new at serde, I still have simple pleasures.)

But now of course I have to make it hard: Is there an easy way to not put the field names for the Ages, heck, maybe not even put any field names, the JSON would be a lot smaller as:

["Thomas",[1743,1801,1826]]

Yes, I can hear people getting frustrated with me already, if I'm trying to save space I could use something better than JSON. Well, I want to understand serde. And those same people are getting even more frustrated with me…

What prompted me to wonder: I want to play with an old very Rust program, one that predates editions, one that predates serde, one that infers lots of dyns—a real historical artifact. Moving it forward has been educational! It now compiles, but panics: it has in the Rust a struct for a Point, with the fields x, y, and z, which is turns into a Vector struct, of course again with labeled fields.

But their JSON data thinks a vector is sensibly stored as [123,234,345], and putting a mere #[derive(Deserialize)] on my Vector struct isn't sufficient.

Is there some untagged struct feature I haven't found? Or do I have to impl my own deserialize method here? (And maybe in a bunch of other panic cases I don't yet know about.)

Suggestions?

Thanks,

-kb

If you take away the field names that is not JSON anymore it is your own home made format.

Likely you could use serde to create your own serialiser/deserializer for your custom format. I have no idea how.

3 Likes

You should be able to get this behaviour if you use a tuple structs (i.e. without named fields). If you don't want to use that then you'll have to write a custom implementation of Serialize/Deserialize (though you can take advantage of the previous observation to simplify them)

1 Like

This problem could, I think, also be solved by writing a Serializer/Deserializer implementation which wraps serde_json and strips out the field names passing through — making it a "non-self-describing format" effectively.

1 Like

The most general way serde provides for custom de/serialization is #[serde(with)]. The serde_with crate provides an easier to use serde_as, which allows you to provide an adapter type which your field/type should be serialized as.

Basically, write a type that derives serialization how you want, then write conversions to/from your primary type. There's a bit more complexity for more optimal minimization of copying, but that's the gist of it, and the linked docs should guide you the rest of the way. (But feel free to ask specific questions if you need more pointers.)

wraps serde_json and strips out the field names

That should work since the derived deserialization supports coming both from "map" and "list" shapes, but I'm mildly worried it would cause subtle issues with other data shapes, especially alternative enum tagging.

Using into and from:

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(from = "AgesSerde", into = "AgesSerde")]
struct Ages {
    birth: i32,
    elected: i32,
    died: i32,
}

#[derive(Debug, Serialize, Deserialize)]
struct AgesSerde(i32, i32, i32);

impl From<AgesSerde> for Ages {
    fn from(value: AgesSerde) -> Self {
        let AgesSerde(birth, elected, died) = value;
        Self {
            birth,
            elected,
            died,
        }
    }
}

impl From<Ages> for AgesSerde {
    fn from(value: Ages) -> Self {
        let Ages {
            birth,
            elected,
            died,
        } = value;
        Self(birth, elected, died)
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(from = "PersonSerde", into = "PersonSerde")]
struct Person {
    name: String,
    ages: Ages,
}

#[derive(Debug, Serialize, Deserialize)]
struct PersonSerde(String, Ages);

impl From<Person> for PersonSerde {
    fn from(value: Person) -> Self {
        let Person { name, ages } = value;
        Self(name, ages)
    }
}

impl From<PersonSerde> for Person {
    fn from(value: PersonSerde) -> Self {
        let PersonSerde(name, ages) = value;
        Self { name, ages }
    }
}

You could also use tuples directly but I think the tuple structs are a bit clearer.

Rust Playground

Actually, it's probably slightly better to put AgesSerde directly into PersonSerde: Rust Playground

2 Likes

It might be non-traditional JSON, but doesn't Javascript allow an array to be a mix of anything? And so JSON can do that, too?

In my example I have an array of two items: a string and another array, and that second array is three numbers.

I think that's legal JSON…

-kb

Playing with that now…

And I have it working in my real code!

Well, actually, no, I don't have my code working. But I got past that one deserialization panic. Now I blow a few lines later, where a different struct using that array thinks it should be a struct.

This next one should fix faster.

Thanks a bunch!

-kb

1 Like

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.