# 7. `serde_cert.rs` — Serde serialization of an X.509 certificate
[← Example index](index.md) · [serde_cert.rs on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/serde_cert.rs)
Load a real PEM certificate from `tests/vectors/`, parse it into an
`Element` tree, serialize the tree to JSON, then deserialize and compare.
Requires `--features serde`.
## Source
```rust,ignore
//! Example: Serde serialization of a real X.509 certificate
//!
//! This example loads `tests/vectors/test_certificate.pem` from the repository root,
//! parses it with synta into an ASN.1 `Element` tree, serializes the tree
//! to JSON using serde, then deserializes the JSON back into a
//! `serde_json::Value` and prints both trees for comparison.
//!
//! Note: `Element<'a>` carries borrowed slices from the original DER buffer
//! and therefore cannot implement `Deserialize`. `serde_json::Value` is used
//! as the round-trip target instead.
//!
//! Run with:
//! cargo run --example serde_cert --features serde
#[cfg(feature = "serde")]
fn main() {
use synta::{Decoder, Element, Encoding};
println!("=== Certificate Serde Round-trip Example ===\n");
// -------------------------------------------------------------------------
// 1. Load and decode the PEM file
// -------------------------------------------------------------------------
let pem = std::fs::read_to_string("tests/vectors/test_certificate.pem")
.expect("Cannot read tests/vectors/test_certificate.pem — run from the repo root");
let der = synta_certificate::pem_to_der(pem.as_bytes())
.into_iter()
.next()
.expect("Failed to decode PEM data");
println!("1. Loaded certificate: {} DER bytes\n", der.len());
// -------------------------------------------------------------------------
// 2. Parse DER → Element tree
// -------------------------------------------------------------------------
let mut decoder = Decoder::new(&der, Encoding::Der);
let element: Element = decoder.decode().expect("Failed to decode ASN.1");
println!("2. Original ASN.1 Element tree (from synta):");
print_element_tree(&element, 1);
let node_count_element = count_element_nodes(&element);
println!(" [{} total nodes]\n", node_count_element);
// -------------------------------------------------------------------------
// 3. Serialize Element → JSON
// -------------------------------------------------------------------------
let json = serde_json::to_string_pretty(&element).expect("Failed to serialize to JSON");
println!("3. Serialized to JSON ({} bytes)\n", json.len());
// -------------------------------------------------------------------------
// 4. Deserialize JSON → serde_json::Value
// -------------------------------------------------------------------------
let value: serde_json::Value = serde_json::from_str(&json).expect("Failed to deserialize JSON");
println!("4. Deserialized serde_json::Value tree (interpreted):");
print_json_tree(&value, 1);
let node_count_json = count_json_nodes(&value);
println!(" [{} total nodes]\n", node_count_json);
// -------------------------------------------------------------------------
// 5. Compare the two trees
// -------------------------------------------------------------------------
println!("5. Comparison:");
if node_count_element == node_count_json {
println!(
" Trees are structurally identical ({} nodes each).",
node_count_element
);
} else {
println!(
" Trees differ: Element tree has {} nodes, JSON tree has {} nodes.",
node_count_element, node_count_json
);
}
// Verify data integrity: re-serialize the Value and parse it back, then
// compare as structured data (key ordering in maps may differ from the
// original serialization, but the data must be equivalent).
let re_json = serde_json::to_string_pretty(&value).expect("Failed to re-serialize");
let re_value: serde_json::Value = serde_json::from_str(&re_json).expect("Failed to re-parse");
if value == re_value {
println!(" JSON round-trip verified: re-serialized data is equivalent.");
} else {
println!(" JSON round-trip MISMATCH: re-serialized data differs.");
}
}
// ---- helpers ----------------------------------------------------------------
/// Recursively print an `Element` tree in an ASN.1-style indented format.
#[cfg(feature = "serde")]
fn print_element_tree(el: &synta::Element<'_>, depth: usize) {
use synta::{bytes_to_hex, Element};
let pad = " ".repeat(depth);
match el {
Element::Boolean(v) => println!("{}Boolean: {}", pad, v.value()),
Element::Integer(v) => println!("{}Integer: {}", pad, bytes_to_hex(v.as_bytes())),
Element::BitString(v) => println!(
"{}BitString: {} bytes ({} unused bits)",
pad,
v.as_bytes().len(),
v.unused_bits()
),
Element::OctetString(v) => {
println!("{}OctetString: {}", pad, bytes_to_hex(v.as_bytes()))
}
Element::Null(_) => println!("{}Null", pad),
Element::Real(v) => println!("{}Real: {}", pad, v.value()),
Element::ObjectIdentifier(v) => println!("{}OID: {}", pad, v),
Element::Utf8String(v) => println!("{}Utf8String: {:?}", pad, v.as_str()),
Element::PrintableString(v) => println!("{}PrintableString: {:?}", pad, v.as_str()),
Element::IA5String(v) => println!("{}IA5String: {:?}", pad, v.as_str()),
Element::UtcTime(v) => println!(
"{}UtcTime: {:02}{:02}{:02}{:02}{:02}{:02}Z",
pad,
v.year % 100,
v.month,
v.day,
v.hour,
v.minute,
v.second
),
Element::GeneralizedTime(v) => println!(
"{}GeneralizedTime: {:04}{:02}{:02}{:02}{:02}{:02}Z",
pad, v.year, v.month, v.day, v.hour, v.minute, v.second
),
Element::NumericString(v) => println!("{}NumericString: {:?}", pad, v.as_str()),
Element::TeletexString(v) => {
println!("{}TeletexString: {}", pad, bytes_to_hex(v.as_bytes()))
}
Element::VisibleString(v) => println!("{}VisibleString: {:?}", pad, v.as_str()),
Element::GeneralString(v) => {
println!("{}GeneralString: {}", pad, bytes_to_hex(v.as_bytes()))
}
Element::UniversalString(v) => println!("{}UniversalString: {:?}", pad, v.as_str()),
Element::BmpString(v) => println!("{}BmpString: {:?}", pad, v.as_str()),
Element::Sequence(seq) => {
let children: Vec<_> = seq.iter().flatten().collect();
println!("{}Sequence ({} elements):", pad, children.len());
for child in children {
print_element_tree(&child, depth + 1);
}
}
Element::Set(set) => {
let children: Vec<_> = set.iter().flatten().collect();
println!("{}Set ({} elements):", pad, children.len());
for child in children {
print_element_tree(&child, depth + 1);
}
}
Element::Tagged(tag, inner) => {
println!("{}Tagged [{:?} {}]:", pad, tag.class(), tag.number());
print_element_tree(inner, depth + 1);
}
Element::Raw(tag, bytes) => {
println!(
"{}Raw [{:?} {}]: {}",
pad,
tag.class(),
tag.number(),
bytes_to_hex(bytes)
);
}
}
}
/// Count total nodes in an `Element` tree (each element counts as one node).
#[cfg(feature = "serde")]
fn count_element_nodes(el: &synta::Element<'_>) -> usize {
use synta::Element;
match el {
Element::Sequence(seq) => {
1 + seq
.iter()
.flatten()
.map(|el| count_element_nodes(&el))
.sum::<usize>()
}
Element::Set(set) => {
1 + set
.iter()
.flatten()
.map(|el| count_element_nodes(&el))
.sum::<usize>()
}
Element::Tagged(_, inner) => 1 + count_element_nodes(inner),
_ => 1,
}
}
/// Recursively print a `serde_json::Value` tree, interpreting the
/// `{"type": "...", "value": ...}` format produced by `Element`'s `Serialize`
/// impl.
#[cfg(feature = "serde")]
fn print_json_tree(val: &serde_json::Value, depth: usize) {
use serde_json::Value;
let pad = " ".repeat(depth);
let type_name = val.get("type").and_then(Value::as_str).unwrap_or("Unknown");
match type_name {
"Sequence" | "Set" => {
if let Some(arr) = val.get("value").and_then(Value::as_array) {
println!("{}{} ({} elements):", pad, type_name, arr.len());
for child in arr {
print_json_tree(child, depth + 1);
}
} else {
println!("{}{}: (empty)", pad, type_name);
}
}
"Tagged" => {
let tag = val.get("tag").cloned().unwrap_or(Value::Null);
let class = tag.get("class").and_then(Value::as_str).unwrap_or("?");
let number = tag.get("number").and_then(Value::as_u64).unwrap_or(0);
println!("{}Tagged [{} {}]:", pad, class, number);
if let Some(inner) = val.get("value") {
print_json_tree(inner, depth + 1);
}
}
_ => {
// Leaf node: print type and its JSON value
let leaf = val.get("value").unwrap_or(&Value::Null);
println!("{}{}: {}", pad, type_name, leaf);
}
}
}
/// Count total nodes in the JSON tree, following the same structure as
/// `count_element_nodes`.
#[cfg(feature = "serde")]
fn count_json_nodes(val: &serde_json::Value) -> usize {
use serde_json::Value;
let type_name = val.get("type").and_then(Value::as_str).unwrap_or("Unknown");
match type_name {
"Sequence" | "Set" => {
let children = val
.get("value")
.and_then(Value::as_array)
.map(|a| a.iter().map(count_json_nodes).sum::<usize>())
.unwrap_or(0);
1 + children
}
"Tagged" => {
let inner_count = val.get("value").map(count_json_nodes).unwrap_or(0);
1 + inner_count
}
_ => 1,
}
}
#[cfg(not(feature = "serde"))]
fn main() {
println!("This example requires the 'serde' feature.");
println!("Run with: cargo run --example serde_cert --features serde");
}
```