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.

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.