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.
Serialize?