1

I am working in a relatively large codebase where options are represented in JSON as arrays, so None is represented in JSON as [] and Some(thing) as [thing]. (Yes, the codebase also contains Haskell, in case you are wondering.) How can I override the default serde_json behaviour, which is to omit optional fields, to match this?

E.g. a struct:

SomeData {
  foo: Some(1),
  bar: None
}

should be serialized to JSON as:

{
  "foo": [1],
  "bar": []
}

Of course, one could theoretically implement custom serialization for each and every optional field in every struct that interacts with the codebase but that would be a huge undertaking, even if it were possible.

There don't seem to be any options in the serde_json serialisation of some and none so I imagine that the solution will be creating a new serializer that inherits almost everything from serde_json apart from the Option serialization and deserialization. Are there any examples of projects that do this? It would also be possible to make a fork but maintaining a fork is never much fun.

Max Murphy
  • 1,701
  • 1
  • 19
  • 29
  • Can you annotate the optional fields in an attribute, or use a custom non-`Option` type for them? – Chayim Friedman Jan 31 '23 at 08:59
  • Precisely this would be "the huge undertaking" and would be error prone, as any new type that was added or imported anywhere would have to have the same annotations added. – Max Murphy Jan 31 '23 at 09:00
  • Can you add one attribute to each struct, on top? – Chayim Friedman Jan 31 '23 at 09:05
  • If you need to modify structs you don't control (i.e., imported ones), you will not be able to avoid writing a custom `Serializer`. You might be able to implement it as a wrapper around another one, e.g., the existing one in `serde_json`. You basically overwrite the `serialize_none` and `serialize_some` functions to emit arrays instead of the default behavior. This is not super complicated but quite boilerplate heavy. – jonasbb Jan 31 '23 at 11:49

1 Answers1

2

Of course, one could theoretically implement custom serialization for each and every optional field in every struct that interacts with the codebase

A custom implementation for each and every field is not necessary. By using serialize_with, you only need one transformation function describing the serialization of any serializable Option<T> as a sequence.

fn serialize_option_as_array<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
    T: Serialize,
    S: Serializer,
{
    let len = if value.is_some() { 1 } else { 0 };
    let mut seq = serializer.serialize_seq(Some(len))?;
    for element in value {
        seq.serialize_element(element)?;
    }
    seq.end()
}

Using it in your struct:

use serde_derive::Serialize;
use serde::ser::{Serialize, Serializer, SerializeSeq};
use serde_json;

#[derive(Debug, Serialize)]
struct SomeData {
    #[serde(serialize_with = "serialize_option_as_array")]
    foo: Option<i32>,
    #[serde(serialize_with = "serialize_option_as_array")]
    bar: Option<u32>,
}


let data = SomeData {
    foo: Some(5),
    bar: None,
};

println!("{}", serde_json::to_string(&data)?);

The output:

{"foo":[5],"bar":[]}

Playground

See also:

E_net4
  • 27,810
  • 13
  • 101
  • 139