Skip to main content

Crate serde_hash

Crate serde_hash 

Source
Expand description

§serde_hash

A Rust library for seamlessly integrating HashIds with Serde serialization and deserialization. This library provides a convenient way to obfuscate numeric IDs in your JSON output without changing your application’s internal data structures.

§Features

  • Extends serde’s Serialize and Deserialize derives – all serde attributes (rename, alias, default, skip, etc.) work alongside hash encoding
  • Automatically convert numeric IDs to hash strings during serialization
  • Transparently decode hash strings back to numeric IDs during deserialization
  • Configurable hash generation with customizable salt, minimum length, and character set
  • Simple attribute-based field marking with #[serde(hash)]
  • Secure random salt generation

§Installation

Add serde_hash to your Cargo.toml:

[dependencies]
serde_hash = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"  # If using JSON serialization

§Supported Types

The hash attribute is only compatible with unsigned integer types and collections of them. It cannot be used with floating-point (f32, f64) or signed integer (i32, i64) types.

CategorySupported Types
Unsigned integersu8, u16, u32, u64, u128, usize
Optional unsigned integersOption<u8>, Option<u16>, …, Option<usize>
Vectors of unsigned integersVec<u8>, Vec<u16>, …, Vec<usize>
Optional vectorsOption<Vec<u8>>, Option<Vec<u16>>, …, Option<Vec<usize>>

§Usage

§Configuration Options

Customize hash settings with SerdeHashOptions. Call .build() once at startup before any serialization.

NameDefault ValueDescription
saltGenerated randomlyThe cryptographic salt used for hash generation
min_length8Minimum length of the generated hash string
alphabetAlphanumeric (a-zA-Z0-9)Characters used for hash encoding

Simplest example:

use serde_hash::hashids::SerdeHashOptions;
SerdeHashOptions::new().build();

A more complete example:

use serde_hash::hashids::SerdeHashOptions;
SerdeHashOptions::new()
.with_salt("hello world")
.with_min_length(10)
.with_alphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
.build();

§Basic Example

Place #[serde_hash] above your derive and mark fields with #[serde(hash)]. All standard serde attributes work alongside hash:

use serde::{Serialize, Deserialize};
use serde_hash::serde_hash;
use serde_hash::hashids::SerdeHashOptions;

#[serde_hash]
#[derive(Serialize, Deserialize, Debug)]
pub struct User {
	#[serde(hash, alias = "identifier")]
	pub id: u64,
	#[serde(rename = "user_name")]
	pub name: String,
	pub age: u8,
}

fn main() {
	SerdeHashOptions::new()
		.with_salt("my-secret-salt")
		.with_min_length(10)
		.with_alphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
		.build();

	let user = User {
		id: 158674,
		name: "Dan Smith".to_string(),
		age: 47,
	};

	let json_string = serde_json::to_string(&user).unwrap();
	println!("{:?} -> {}", user, json_string);
	// Output: User { id: 158674, ... } -> {"id":"qKknODM7Ej","user_name":"Dan Smith","age":47}
}

Notice how #[serde(hash, alias = "identifier")] works – hash triggers hash encoding while alias is handled by serde normally. The rename on name also works as expected.

§Deserialization Example

Hash strings are transparently decoded back to numeric IDs:

use serde::{Serialize, Deserialize};
use serde_hash::serde_hash;
use serde_hash::hashids::SerdeHashOptions;

#[serde_hash]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct User {
	#[serde(hash)]
	pub id: u64,
	pub name: String,
	pub age: u8,
}

fn main() {
	SerdeHashOptions::new()
		.with_salt("my-secret-salt")
		.with_min_length(10)
		.with_alphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
		.build();

	let user = User { id: 158674, name: "Dan Smith".to_string(), age: 47 };
	let json = serde_json::to_string(&user).unwrap();

	let deserialized: User = serde_json::from_str(&json).unwrap();
	assert_eq!(user, deserialized);
}

§Using with Vectors and Options

#[serde(hash)] works with Vec<T>, Option<T>, and Option<Vec<T>> where T is an unsigned integer:

use serde::{Serialize, Deserialize};
use serde_hash::serde_hash;
use serde_hash::hashids::SerdeHashOptions;

#[serde_hash]
#[derive(Serialize, Deserialize, Debug)]
pub struct DataWithCollections {
	#[serde(hash)]
	pub id: u64,
	pub name: String,
	#[serde(hash)]
	pub values: Vec<u8>,
	#[serde(hash)]
	pub optional_id: Option<u32>,
	#[serde(hash)]
	pub optional_values: Option<Vec<u16>>,
}

fn main() {
	SerdeHashOptions::new()
		.with_salt("my-secret-salt")
		.build();

	let data = DataWithCollections {
		id: 158674,
		name: "Dan Smith".to_string(),
		values: vec![1, 2, 3, 4, 5],
		optional_id: Some(42),
		optional_values: Some(vec![10, 20, 30]),
	};

	let json = serde_json::to_string(&data).unwrap();
	println!("{}", json);

	let deserialized: DataWithCollections = serde_json::from_str(&json).unwrap();
	println!("{:?}", deserialized);
}

§Generating Secure Salt

For production use, generate a cryptographically secure random salt:

use serde_hash::{hashids::SerdeHashOptions, salt::generate_salt};

let salt = generate_salt();
SerdeHashOptions::new()
.with_salt( & salt)
.with_min_length(10)
.build();

§How It Works

The #[serde_hash] attribute macro runs before serde’s derive macros. It transforms #[serde(hash)] into serde’s #[serde(with = "...")] attribute, pointing to built-in serialize/deserialize functions that handle hash encoding. This means:

  • Serde’s own derive generates the Serialize/Deserialize implementations
  • All serde attributes (rename, alias, default, skip, flatten, etc.) work normally
  • Hash encoding is applied only to the marked fields via serde’s with mechanism

§Why Use serde_hash?

  • Obfuscation: Hide your internal database IDs from API consumers
  • Serde compatible: Works with all serde attributes and any serde-based format (JSON, TOML, MessagePack, etc.)
  • Predictability: Unlike UUID generation, the same ID always hashes to the same string with the same settings
  • Transparency: Your application code works with numeric IDs while your API exposes hash strings
  • Simplicity: Add one attribute macro and configure once

§License

MIT License


This project is built with Rust and uses the hash-ids crate for hash ID generation.

Modules§

hashids
salt
serde_impl

Attribute Macros§

hash
serde_hash
Attribute macro that enables #[serde(hash)] on struct fields.

Derive Macros§

HashIds