Add an additional field while serializing
Asked Answered
J

3

7

In Serde serializers, how to add an additional field:

#[derive(Serialize)]
struct X {
  a: u32,
  b: u32,
  c: u32,
}

I want to add to JSON serialization field d with value "qwe". How without writing completely a serializer for X from scratch?

Julianejuliann answered 23/2, 2023 at 19:36 Comment(0)
J
7

Assuming that you don't mind that the field d exists, but you just don't want it taking space in your struct, you can make it zero-sized and use the serialize_with attribute to emit the desired data when serializing:

#[derive(Serialize)]
struct X {
  a: u32,
  b: u32,
  c: u32,
  #[serde(serialize_with = "emit_qwe")]
  d: (),
}

fn emit_qwe<S: Serializer>(_: &(), s: S) -> Result<S::Ok, S::Error> {
    s.serialize_str("qwe")
}

Playground

Jehias answered 23/2, 2023 at 19:46 Comment(3)
And if I need not "qwe", but value of c in hexadecimal? (that's more similar to my real task)Julianejuliann
In fact, I need to output c in serialization twice, in different representations.Julianejuliann
@Julianejuliann That's a different task than in the question, one for which the approach in this answer won't work.Jehias
B
7

A custom Serialize implementation isn't so bad:

use serde::ser::{Serialize, Serializer, SerializeStruct};

struct X {
  a: u32,
  b: u32,
  c: u32,
}

impl Serialize for X {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut x = serializer.serialize_struct("X", 4)?;
        x.serialize_field("a", &self.a)?;
        x.serialize_field("b", &self.b)?;
        x.serialize_field("c", &self.c)?;
        x.serialize_field("d", "qwe")?;
        x.end()
    }
}
Broomstick answered 23/2, 2023 at 19:52 Comment(1)
The problem with this approach is that it is brittle. Fields need to be enumerated by hand, so if a field is added into the struct, you need to remember to update Serialize. Additionally, string names might need to be kept in sync with field names.Misdo
P
6

You can create a separate struct that's #[derive(Serialize)] and copy everything in your struct over

impl Serialize for X {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        #[derive(Serialize)]
        struct XSerialize {
            a: u32,
            b: u32,
            c: u32,
            d: &'static str,
        }
        
        XSerialize {
            a: self.a,
            b: self.b,
            c: self.c,
            d: "only at serialization"
        }.serialize(serializer)
    }
}

This should allow you to compute a value for d as you specify in the comments.

Panjandrum answered 23/2, 2023 at 19:49 Comment(2)
I like this approach slightly better that the one proposed by kmdreko, but all drawbacks are till there - field names need to be spelled explicitly twice, which is likely de-sync in any real program very quickly.Misdo
You can use destructuring to at least get an error if any field names are changed, though in general you'd expect serializations to have stable field names.Panjandrum

© 2022 - 2024 — McMap. All rights reserved.