Dynamic Values
Facet supports dynamic, schema-less data through facet_value::Value — a pointer-sized type that can hold any structured data. This enables powerful patterns like two-phase deserialization, mixed static/dynamic comparisons, and deferred parsing.
facet-value: the dynamic value type
facet_value::Value is facet's equivalent to serde_json::Value, but format-agnostic and more memory-efficient. It supports eight value types: Null, Bool, Number, String, Bytes, Array, Object, and DateTime.
Deserialize to value, then to a type
A common pattern is to deserialize into Value first, then convert to a concrete type later. This is useful when:
- You don't know the schema at compile time
- You want to inspect the data before choosing a target type
- You're building configuration layers that merge multiple sources
use facet:: Facet ;
use facet_value::{ Value , from_value};
# [ derive ( Facet , Debug )]
struct Config {
host : String ,
port : u16 ,
}
// First, deserialize JSON into a dynamic Value
let json = r#"{"host": "localhost", "port": 8080}"# ;
let value: Value = facet_json:: from_str ( json) ?;
// Inspect or transform the value if needed
println! ( "Keys: {:?}" , value. as_object (). map ( |o| o. keys (). collect ::< Vec < _ >>()));
// Then convert to a concrete type
let config: Config = from_value ( & value) ?;
println! ( "{:?}" , config);
Partial tree extraction
You can also extract just part of a Value tree into a typed struct:
use facet:: Facet ;
use facet_value::{ Value , from_value};
# [ derive ( Facet , Debug )]
struct DatabaseConfig {
host : String ,
port : u16 ,
}
let json = r#"{
"app": {"name": "myapp"},
"database": {"host": "db.example.com", "port": 5432},
"logging": {"level": "info"}
}"# ;
let value: Value = facet_json:: from_str ( json) ?;
// Extract just the database section
if let Some ( db_value) = value. as_object (). and_then ( |o| o. get ( "database" )) {
let db_config: DatabaseConfig = from_value ( db_value) ?;
println! ( "Database: {:?}" , db_config);
}
assert_same!: compare anything to anything
facet_assert::assert_same! compares values structurally using reflection — no PartialEq required. The powerful part? It can compare values of different types, including a Value against a typed struct.
Dynamic vs typed comparison
This is incredibly useful for testing: verify that your JSON matches the expected typed structure without manually constructing the typed value:
use facet:: Facet ;
use facet_assert:: assert_same;
use facet_value:: Value ;
# [ derive ( Facet )]
struct User {
name : String ,
age : u32 ,
}
// Your actual typed value
let user = User { name : "Alice" . into (), age : 30 };
// Expected data as a dynamic Value (maybe from a test fixture)
let expected: Value = facet_json:: from_str ( r#"{"name": "Alice", "age": 30}"# ) ?;
// Compare them directly — different types, same structure!
assert_same! ( user, expected);
Cross-Version DTO comparison
Compare DTOs across API versions without implementing PartialEq between them:
# [ derive ( Facet )]
struct UserV1 { name : String , age : u32 }
# [ derive ( Facet )]
struct UserV2 { name : String , age : u32 } // Same fields, different type
let v1 = UserV1 { name : "Bob" . into (), age : 25 };
let v2 = UserV2 { name : "Bob" . into (), age : 25 };
assert_same! ( v1, v2); // Works! Compares by structure, not type
Rich diff output
When values differ, you get a detailed, colored diff showing exactly what's different:
assertion `assert_same!(left, right)` failed
.name: "Alice" → "Bob"
.age: 30 → 25
RawJson: defer parsing
facet_json::RawJson captures unparsed JSON text, letting you delay or skip deserialization of parts of a document. The JSON text can be borrowed (zero-copy) or owned.
Use cases
- Unknown schema: Part of your JSON has an unknown or highly variable structure
- Pass-through: You need to store/forward JSON without parsing it
- Lazy parsing: Defer expensive parsing until you know you need it
- Selective parsing: Only parse the parts you care about
Basic usage
use facet:: Facet ;
use facet_json:: RawJson ;
# [ derive ( Facet , Debug )]
struct ApiResponse < ' a > {
status : u32 ,
// We don't know what shape `data` has, so keep it as raw JSON
data : RawJson < ' a >,
}
let json = r#"{"status": 200, "data": {"nested": [1, 2, 3], "complex": true}}"# ;
let response: ApiResponse = facet_json:: from_str ( json) ?;
assert_eq! ( response. status , 200 );
// The data field is still raw JSON text, not parsed
assert_eq! ( response. data . as_str (), r#"{"nested": [1, 2, 3], "complex": true}"# );
Zero-Copy borrowing
When possible, RawJson borrows from the input string (zero allocation):
use facet_json:: RawJson ;
use std:: borrow:: Cow ;
# [ derive ( Facet )]
struct Envelope < ' a > {
kind : String ,
payload : RawJson < ' a >, // Borrows from input when possible
}
let json = r#"{"kind": "event", "payload": {"type": "click", "x": 100}}"# ;
let envelope: Envelope = facet_json:: from_str ( json) ?;
// payload.0 is Cow::Borrowed — no allocation for the payload!
Owned rawJson
If you need to outlive the input, convert to owned:
let owned: RawJson < ' static > = envelope. payload . into_owned ();
Later parsing
Parse the raw JSON when you're ready:
# [ derive ( Facet )]
struct ClickEvent {
r#type : String ,
x : i32 ,
}
// Parse the raw JSON into a concrete type
let event: ClickEvent = facet_json:: from_str ( response. data . as_str ()) ?;
Combining these patterns
These features compose naturally:
use facet:: Facet ;
use facet_assert:: assert_same;
use facet_json:: RawJson ;
use facet_value::{ Value , from_value};
# [ derive ( Facet )]
struct Wrapper < ' a > {
version : u32 ,
data : RawJson < ' a >, // Defer parsing
}
# [ derive ( Facet , Debug )]
struct Payload {
items : Vec < String >,
}
// Parse outer structure, defer inner
let json = r#"{"version": 1, "data": {"items": ["a", "b", "c"]}}"# ;
let wrapper: Wrapper = facet_json:: from_str ( json) ?;
// Parse inner to Value for inspection
let data_value: Value = facet_json:: from_str ( wrapper. data . as_str ()) ?;
// Convert to typed when ready
let payload: Payload = from_value ( & data_value) ?;
// Verify against expected
let expected = Payload { items : vec! [ "a" . into (), "b" . into (), "c" . into ()] };
assert_same! ( payload, expected);
Next steps
- See the From Value showcase for more
Value→ typed examples - See the Assertions showcase for
assert_same!examples - Check out facet-value on docs.rs for the full API