Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to transform fields during serialization using Serde?

Tags:

rust

serde

How can I apply a transformation to a field before serialization?

For example, how can I ensure that the fields lat and lon in this struct definition are rounded to at most 6 decimal places before being serialized?

#[derive(Debug, Serialize)]
struct NodeLocation {
    #[serde(rename = "nodeId")]
    id: u32,
    lat: f32,
    lon: f32,
}
like image 479
Synesso Avatar asked Sep 08 '16 06:09

Synesso


People also ask

What is Serde serialization?

Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. The Serde ecosystem consists of data structures that know how to serialize and deserialize themselves along with data formats that know how to serialize and deserialize other things.

Is Serde fast?

Serde. Serde, the incumbent serialization/deserialization library, is elegant, flexible, fast to run, and slow to compile.

What is Serde JSON?

The Hive JSON SerDe is commonly used to process JSON data like events. These events are represented as single-line strings of JSON-encoded text separated by a new line. The Hive JSON SerDe does not allow duplicate keys in map or struct key names.


1 Answers

The serialize_with attribute

You can use the serialize_with attribute to provide a custom serialization function for your field:

use serde::{Serialize, Serializer}; // 1.0.104

fn round_serialize<S>(x: &f32, s: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    s.serialize_f32(x.round())
}

#[derive(Debug, Serialize)]
pub struct NodeLocation {
    #[serde(rename = "nodeId")]
    id: u32,
    #[serde(serialize_with = "round_serialize")]
    lat: f32,
    #[serde(serialize_with = "round_serialize")]
    lon: f32,
}

(I've rounded to the nearest integer to avoid the topic "what is best way to round a float to k decimal places").

Implement serde::Serialize

The other semi-manual approach is to create a separate struct with auto-derived serialization, and implement your serialization using that:

use serde::{Serialize, Serializer}; // 1.0.104

#[derive(Debug)]
pub struct NodeLocation {
    id: u32,
    lat: f32,
    lon: f32,
}

impl serde::Serialize for NodeLocation {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Implement your preprocessing in `from`.
        RoundedNodeLocation::from(self).serialize(s)
    }
}

#[derive(Debug, Serialize)]
pub struct RoundedNodeLocation {
    #[serde(rename = "nodeId")]
    id: u32,
    lat: f32,
    lon: f32,
}

impl<'a> From<&'a NodeLocation> for RoundedNodeLocation {
    fn from(other: &'a NodeLocation) -> Self {
        Self {
            id: other.id,
            lat: other.lat.round(),
            lon: other.lon.round(),
        }
    }
}

Notably, this allows you to also add or remove fields as the "inner" serialized type can do basically whatever it wants.

like image 76
krdln Avatar answered Sep 25 '22 05:09

krdln