Macro with_suffix

Source
macro_rules! with_suffix {
    ($module:ident $suffix:expr) => { ... };
    ($vis:vis $module:ident $suffix:expr) => { ... };
}
Expand description

Serialize with an added suffix on every field name and deserialize by trimming away the suffix.

You can set the visibility of the generated module by suffixing the module name with a module visibility. with_suffix!(pub(crate) suffix_foo "_foo"); creates a module with pub(crate) visibility. The visibility is optional and by default pub(self), i.e., private visibility is assumed.

Note: Use of this macro is incompatible with applying the deny_unknown_fields attribute on the container. While deserializing, it will always warn about unknown fields, even though they are processed by the with_suffix wrapper. More details can be found in this issue.

ยงExample

Factorio Prototype tables like to use suffixes to group related fields. In simplified form, their JSON representation may resemble the following:

{
  "frames": 4,
  "spritesheet": "normal",
  "frames_frozen": 1,
  "spritesheet_frozen": "frozen",
  "frames_visualization": 2,
  "spritesheet_visualization": "vis",
}

In Rust, we would ideally like to model this data as a couple of SpriteData structs, rather than repeating the fields of SpriteData for each suffix.

struct Graphics {
    normal: SpriteData,
    frozen: SpriteData,
    visualization: SpriteData,
}

struct SpriteData {
    frames: u64,
    spritesheet: String,
}

This with_suffix! macro produces an adapter that adds a suffix onto field names during serialization and trims away the suffix during deserialization. An implementation for the mentioned situation would use with_suffix! like this:

use serde::{Deserialize, Serialize};
use serde_with::with_suffix;

#[derive(Serialize, Deserialize)]
struct Graphics {
    #[serde(flatten)]
    normal: SpriteData,
    #[serde(flatten, with = "suffix_frozen")]
    frozen: SpriteData,
    #[serde(flatten, with = "suffix_visualization")]
    visualization: SpriteData,
}

#[derive(Serialize, Deserialize)]
struct SpriteData {
    frames: u64,
    spritesheet: String,
}

with_suffix!(suffix_frozen "_frozen");
// You can also set the visibility of the generated suffix module, the default is private.
with_suffix!(pub suffix_visualization "_visualization");

fn main() {
    let g = Graphics {
        normal: SpriteData {
            frames: 4,
            spritesheet: "normal".to_owned(),
        },
        frozen: SpriteData {
            frames: 1,
            spritesheet: "frozen".to_owned(),
        },
        visualization: SpriteData {
            frames: 2,
            spritesheet: "vis".to_owned(),
        },
    };

    let j = serde_json::to_string_pretty(&g).unwrap();
    println!("{}", j);
}