configurable_serde

Crates.ioconfigurable_serde
lib.rsconfigurable_serde
version0.1.0
created_at2025-09-23 12:52:06.795581+00
updated_at2025-09-23 12:52:06.795581+00
descriptionA proc-macro to apply reusable serde configurations.
homepagehttps://github.com/sfisol/configurable_serde
repositoryhttps://github.com/sfisol/configurable_serde
max_upload_size
id1851400
size19,672
MichaƂ Pokrywka (sfisol)

documentation

https://docs.rs/configurable_serde

README

Configurable Serde

A crate providing a procedural macro to apply reusable serde configurations.

The core of this crate is the #[configure_serde] attribute macro, which generates #[serde(...)] attributes for you.

There is also a generator macro provided create_config!, which builds a wrapper to apply #[configure_serde] to all wrapped structs and enums.

NOTE: While it is tested and working, this is an early stage of the project so it lacks many possible configurable parameters and also may be incompatible with more complicated structs and enums.

TL;DR

Generate serde configuration applier:

use configurable_serde::create_config;
use serde::{Serialize, Deserialize};

// Generate serde configuration applier:

create_config! {
   my_api_models,

   struct_rename_all = "camelCase",
   enum_rename_all = "SCREAMING_SNAKE_CASE",
   skip_if_none,
   deny_unknown_fields
}

// Apply defined serde configuration to multiple structs or enums:

my_api_models! {
    #[derive(Serialize, Deserialize)]
    struct User { // camelCase and deny_unknown_fields is applied here
        name: String,
        surname: Option<String>, // skip_if_none is applied here
        vehicle: Vehicle,
    }

    #[derive(Serialize, Deserialize)]
    enum Vehicle { // SCREAMING_SNAKE_CASE is applied here
        Car,
        Bicycle,
        ElectricScooter,
    }
}

So this will be expanded to:

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct User {
    name: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    surname: Option<String>,
    vehicle: Vehicle,
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE", deny_unknown_fields)]
enum Vehicle {
    Car,
    Bicycle,
    ElectricScooter,
}

Note, that if you want to use your applier across a whole project, you need to place create_config! before any mod directives for modules using the applier. This is because the #[macro_export] term generated by the other macro is not recognized early enough.

Also note this may confuse the rust analyzer too. To overcome this, place your configs in a separate file, f.ex. serde_configs.rs and import it before any usage with #[macro_use] mod serde_configs;.

Supported parameters

  • rename_all: Option<String>

    A general rename rule for both structs and enums. Can be overridden by struct_rename_all or enum_rename_all.

  • struct_rename_all: Option<String>

    A specific rename rule for struct fields.

  • enum_rename_all: Option<String>

    A specific rename rule for enum variants.

  • skip_if_none: bool

    If present, adds #[serde(default, skip_serializing_if = "Option::is_none")] to any field of type Option<T>.

  • deny_unknown_fields: bool

    If present, adds #[serde(deny_unknown_fields)] to the container.

Manual Usage of configure_serde macro

use configure_serde::configure_serde;
use serde::{Serialize, Deserialize};

#[configure_serde(rename_all = "camelCase", skip_if_none)]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct User {
    user_id: String,
    display_name: Option<String>,
}

// The struct will be serialized with camelCase fields, and `display_name`
// will be omitted if it is `None`.

let user = User { user_id: "u-123".to_string(), display_name: None };
let json = serde_json::to_string(&user).unwrap();

assert_eq!(json, r#"{"userId":"u-123"}"#);

Reusing Configurations without create_config

You can define your own applier manually. It is just a declarative macro (macro_rules!) that applies the #[configure_serde] attribute with your desired settings.

Reusable configuration which wraps one item

use configurable_serde::configure_serde;
use serde::{Serialize, Deserialize};

/// Defines a reusable configuration named `apply_api_config`:
macro_rules! apply_api_config {
    ($item:item) => {
        #[configure_serde(
            struct_rename_all = "camelCase",
            enum_rename_all = "SCREAMING_SNAKE_CASE",
            skip_if_none,
            deny_unknown_fields
        )]
        $item
    };
}

// Now, apply this configuration to a struct.
apply_api_config! {
    #[derive(Serialize, Deserialize, Debug, PartialEq)]
    pub struct Product {
        product_id: String,
        stock_count: Option<u32>,
    }
}

// And to an enum.
apply_api_config! {
    #[derive(Serialize, Deserialize, Debug, PartialEq)]
    pub enum Status {
        InStock,
        Backordered,
        Discontinued,
    }
}

// Test the struct serialization
let product = Product {
    product_id: "prod-456".to_string(),
    stock_count: Some(50)
};

let product_json = serde_json::to_string(&product).unwrap();
assert_eq!(product_json, r#"{"productId":"prod-456","stockCount":50}"#);

// Test the enum serialization
let status = Status::InStock;

let status_json = serde_json::to_string(&status).unwrap();
assert_eq!(status_json, r#""IN_STOCK""#);

Reusable configuration which wraps multiple items

This is the same as what you get using the create_config! macro, but it let's you to place the definition in any place in your crate (that is: #[macro_export] works).

#[macro_export]
macro_rules! my_api_models {
    ($($item:item)*) => {
        $(
            #[configurable_serde::configure_serde(
                struct_rename_all = "camelCase",
                enum_rename_all = "SCREAMING_SNAKE_CASE",
                skip_if_none
            )]
            $item
        )*
    };
}
Commit count: 4

cargo fmt