Serde serialise fn calls as fields

Hi there. I have a struct with various associated fns implemented on it. Is it possible to add the results of these fns to the serialised output.

A concrete example:

#[derive(Serialize)]
struct Filter {
  #[serde(rename="locationIds")]
  location_ids: Vec<usize>,
  #[serde(rename="categoryIds")]
  category_ids: Vec<usize>,
}

impl Filter {
  fn is_empty(&self) -> bool {
    self.location_ids.is_empty() && self.category_ids.is_empty()
  }
}

and I would like to serialise this into (for example):
{ locationIds: [], categoryIds: [], isEmpty: true}

I don't want to cache the is_empty field.

Is this possible without writing a custom Serializer?

Thanks!

Well, a custom Serializer would never be necessary. A custon Serialize implementation is an option. Taking inspiration from this example from the serde docs

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

struct Color {
    r: u8,
    g: u8,
    b: u8,
}

impl Serialize for Color {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // 3 is the number of fields in the struct.
        let mut state = serializer.serialize_struct("Color", 3)?;
        state.serialize_field("r", &self.r)?;
        state.serialize_field("g", &self.g)?;
        state.serialize_field("b", &self.b)?;
        state.end()
    }
}

and also comparing with the output of cargo expand on this example which doesn't seem to do anything more either

        #[automatically_derived]
        impl _serde::Serialize for Filter {
            fn serialize<__S>(&self, __serializer: __S)
                -> _serde::__private::Result<__S::Ok, __S::Error> where
                __S: _serde::Serializer {
                let mut __serde_state =
                    match _serde::Serializer::serialize_struct(__serializer,
                            "Filter", false as usize + 1 + 1) {
                        _serde::__private::Ok(__val) => __val,
                        _serde::__private::Err(__err) => {
                            return _serde::__private::Err(__err);
                        }
                    };
                match _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "locationIds", &self.location_ids) {
                    _serde::__private::Ok(__val) => __val,
                    _serde::__private::Err(__err) => {
                        return _serde::__private::Err(__err);
                    }
                };
                match _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                        "categoryIds", &self.category_ids) {
                    _serde::__private::Ok(__val) => __val,
                    _serde::__private::Err(__err) => {
                        return _serde::__private::Err(__err);
                    }
                };
                _serde::ser::SerializeStruct::end(__serde_state)
            }
        }

it looks like such a custom implementation would be fairly straightforward: The derived impl above should be equivalent to

impl Serialize for Filter {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("Filter", 2)?;
        state.serialize_field("locationIds", &self.location_ids)?;
        state.serialize_field("categoryIds", &self.category_ids)?;
        state.end()
    }
}

and adding the additional entry is straightforward:

impl Serialize for Filter {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("Filter", 3)?;
        state.serialize_field("locationIds", &self.location_ids)?;
        state.serialize_field("categoryIds", &self.category_ids)?;
        state.serialize_field("isEmpty", &self.is_empty())?;
        state.end()
    }
}
1 Like

Thanks @steffahn - I'm confused by your first sentence. Is there a way of doing this without writing a custom Serialise implementation? I'm actually doing exactly as your proposed solution is doing, but I was hoping I could do some clever trick to save the legwork :-).

Well, Serializer with an “r” is a different trait, pretty tedious to implement, which will be implemented by crates that provide the encoding end of serialization, e.g. serde-json offers a Serializer.

Ah! Sorry, I meant Serialize, not Serializer. I didn't spot the r. Thanks :slight_smile:

Course there is! Just make it convert to another struct first. (this one can be kept private to the module)

// Notice that using `#[serde(into)]` requires `Clone`.
#[derive(Serialize, Clone)]
#[serde(into="SerializedFilter")]
struct Filter {
  location_ids: Vec<usize>,
  category_ids: Vec<usize>,
}

// Note: since adding this second struct also effectively decouples the
// field names from the rest of the code I've also switched to `rename_all`,
// but you're free to do whatever
#[derive(Serialize)]
#[serde(rename_all="camelCase")]
struct SerializedFilter {
  location_ids: Vec<usize>,
  category_ids: Vec<usize>,
  is_empty: bool,
}

impl From<Filter> for SerializedFilter {
    fn from(filter: Filter) -> Self {
        SerializedFilter {
            is_empty: filter.is_empty(),
            location_ids: filter.location_ids,
            category_ids: filter.category_ids,
        }
    }
}

Playground link.


If you don't want to impl Clone for Filter you can still use a similar technique to write a custom Serialize impl that simply delegates to another type's Serialize (rather than having to call .serialize_struct() by hand and etc.)

How to do it without Clone.

3 Likes

This is exactly what I'm looking for.

I tried it locally but ran into an error when serialising:

warning: `model` (lib) generated 2 warnings                                                                               
   17    Compiling dashboard-api v0.1.0 (/Users/coliny/Dev/com.qfi.health/src/rust/dashboard-api)                               
   18 error[E0275]: overflow evaluating the requirement `&mut Vec<u8>: Sized`                                                   
   19   |                                                                                                                       
   20   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`dashboard_api`)
   21   = note: required because of the requirements on the impl of `SerializeMap` for `Compound<&mut Vec<u8>, PrettyFormatter>`           
   22   = note: 127 redundant requirements hidden                                                                                                        
   23   = note: required because of the requirements on the impl of `SerializeMap` for `FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatM
   24 apSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSeria
   25 lizeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap
   26 <FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMa
   27 pSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerial
   28 izeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<
   29 FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMap
   30 SerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSeriali
   31 zeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<F
   32 latMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapS
   33 erializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializ
   34 eMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<Fl
   35 atMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSe
   36 rializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerialize
   37 Map<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<Fla
   38 tMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSer
   39 ializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeM
   40 ap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<Compound<&mut Vec<u8>, P
   41 rettyFormatter>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`    
   42   = note: required because of the requirements on the impl of `filter::_::_serde::Serializer` for `FlatMapSerializer<FlatMapSerializeMap<FlatMapSer
   43 ializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeM
   44 ap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<Flat
   45 MapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSeri
   46 alizeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMa
   47 p<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatM
   48 apSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSeria
   49 lizeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap
   50 <FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMa
   51 pSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerial
   52 izeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<
   53 FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMap
   54 SerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSeriali
   55 zeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<F
   56 latMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapS
   57 erializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializ
   58 eMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<Fl
   59 atMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSerializeMap<FlatMapSe
   60 rializeMap<Compound<&mut Vec<u8>, PrettyFormatter>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
   61 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`                                                                                                                   
   62                                                                                                                                                    
   63 For more information about this error, try `rustc --explain E0275`.                                                                                
   64 error: could not compile `dashboard-api` due to previous error                                                                             
   65                                                                                              

I need to debug it locally first, but thanks for the pointer!

ah no, it's because I was using the origin struct inside the new struct with #[serde(flatten)]. Of course that's a recursive issue :slight_smile:

On hindsight, I think I prefer the first solution using a wrapper struct which embeds the original struct, just for the type safety of the thing.