2 stable releases
Uses new Rust 2024
| 1.0.1 | Oct 9, 2025 |
|---|---|
| 1.0.0 | Oct 7, 2025 |
#475 in Encoding
Used in whatsapp-business-rs
79KB
1.5K
SLoC
serde_metaform
A high-performance serde serializer for the hybrid "Form + JSON" encoding format used by APIs like Meta’s (WhatsApp Business, Instagram Messaging, etc.).
⚠️ Warning: This is not a standard
application/x-www-form-urlencodedserializer. It produces a specialized encoding where values are JSON-encoded before being percent-encoded. Do not use this for ordinary HTML form submissions.
🧩 What Is “Form + JSON” Encoding?
This format looks like a typical form payload (key=value&key2=value2), but each value is actually JSON.
- Top-level data is represented as key–value pairs.
- Keys are percent-encoded strings.
- Values are serialized as JSON strings, then the entire JSON string is percent-encoded.
This hybrid structure allows complex nested JSON to be transmitted in APIs that only accept form-like bodies.
Example
Standard form encoding:
user_id=123&tags=rust&tags=serde
Form + JSON encoding:
user_id=123&
profile=%7B%22username%22%3A%22jdoe%22%2C%22tags%22%3A%5B%22rust%22%2C%22serde%22%5D%7D
The profile value above is the percent-encoded JSON string:
{"username":"jdoe","tags":["rust","serde"]}
🚀 Features
- Single-Pass Serialization: No intermediate
serde_json::Value, no double parsing. - Zero-Copy: Streams directly into the output writer with minimal allocation.
- Full
serdeIntegration: Works out of the box with#[derive(Serialize)]. - Nested Data Support: Handles structs, enums, sequences, and maps cleanly.
- Battle-Tested: Used internally in
whatsapp-business-rs.
🧠 Usage
Add to your Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_metaform = "1"
Then simply serialize your data:
use serde::Serialize;
use serde_metaform::to_string;
#[derive(Serialize)]
struct Message<'a> {
recipient: &'a str,
content: Content<'a>,
}
#[derive(Serialize)]
struct Content<'a> {
#[serde(rename = "type")]
message_type: &'a str,
text: &'a str,
buttons: Vec<Button<'a>>,
}
#[derive(Serialize)]
struct Button<'a> {
id: &'a str,
title: &'a str,
}
fn main() -> Result<(), serde_metaform::error::Error> {
let msg = Message {
recipient: "1234567890",
content: Content {
message_type: "interactive",
text: "Choose an option:",
buttons: vec![
Button { id: "opt1", title: "Option 1" },
Button { id: "opt2", title: "Option 2" },
],
},
};
let encoded = to_string(&msg)?;
println!("{encoded}");
// Output (simplified, actual output is percent-encoded):
// recipient=1234567890&
// content={"type":"interactive","text":"Choose an option:","buttons":[{"id":"opt1","title":"Option 1"},{"id":"opt2","title":"Option 2"}]}
Ok(())
}
⚡ Performance
serde_metaform was originally extracted from a production WhatsApp integration layer,
where JSON bodies were pre-built before conversion to the hybrid format.
This crate eliminates that two-step process.
| Benchmark | Description | Mean Time | Relative |
|---|---|---|---|
json_value |
struct → JSON Value → form | 35.5 µs | 1.00× |
json_pipeline |
struct → Bytes → form (old pipeline) | 23.6 µs | 1.5× faster |
from_struct |
struct → form (serde_metaform) |
14.0 µs | 2.53× faster |
Real-world WhatsApp payloads typically gain 35–45 % throughput improvement.
Even under realistic, non-pretty-printed conditions, serde_metaform reduces total serialization time by ~40% and cuts memory allocations roughly in half. Performance gains scale further with larger or deeply nested payloads.
📜 License
Licensed under either of:
at your option.
Dependencies
~0.5–1MB
~19K SLoC