Loading pageā¦
Rust walkthroughs
Loading pageā¦
serde_json::Value differ from serde_json::RawValue for unstructured JSON handling?serde_json::Value and serde_json::RawValue serve fundamentally different purposes for handling unstructured JSON. Value is a fully parsed, owned representation that deserializes JSON into a structured enum (Object, Array, String, Number, Bool, Null), enabling traversal and modification but requiring allocation for every element. RawValue is an opaque reference to unparsed JSON text that delays deserializationāit stores the raw string bytes and only parses when explicitly needed. This makes RawValue significantly more efficient for passthrough scenarios where you need to preserve JSON without modifying it, while Value is the right choice when you need to inspect, query, or transform the data.
use serde_json::{Value, json};
fn basic_value() {
// Create a Value from a JSON literal
let value: Value = json!({
"name": "Alice",
"age": 30,
"active": true,
"tags": ["rust", "json"],
"metadata": null
});
// Value is fully parsed and traversable
println!("Name: {}", value["name"]);
println!("Age: {}", value["age"]);
println!("First tag: {}", value["tags"][0]);
// Value is mutable
let mut value = value;
value["age"] = json!(31);
value["city"] = json!("Seattle");
// Can traverse dynamically
if let Some(tags) = value["tags"].as_array() {
for tag in tags {
println!("Tag: {}", tag);
}
}
// Check types
if let Some(name) = value["name"].as_str() {
println!("Name as string: {}", name);
}
if let Some(age) = value["age"].as_u64() {
println!("Age as u64: {}", age);
}
}Value parses JSON into a structured, traversable, and mutable format.
use serde_json::value::RawValue;
use serde_json::json;
fn basic_raw_value() {
// RawValue wraps unparsed JSON text
let raw: Box<RawValue> = RawValue::from_string(
r#"{"name": "Alice", "age": 30}"#.to_string()
);
// The JSON is NOT parsed - just stored as a string
// You cannot traverse or query it directly
// Access the raw string
println!("Raw JSON: {}", raw.get());
// Can be serialized without re-parsing
let output = serde_json::to_string(&raw).unwrap();
println!("Output: {}", output);
// Parse it when needed
let parsed: serde_json::Value = serde_json::from_str(raw.get()).unwrap();
println!("Name: {}", parsed["name"]);
}RawValue stores JSON as text, delaying parsing until explicitly needed.
use serde_json::{Value, json};
use serde_json::value::RawValue;
use std::mem::size_of;
fn memory_representation() {
// Value is a recursive enum - each element allocates
let value: Value = json!({
"users": [
{"name": "Alice", "id": 1},
{"name": "Bob", "id": 2},
{"name": "Charlie", "id": 3}
]
});
// Value allocates:
// - 1 Object (HashMap)
// - 1 Array (Vec)
// - 3 Objects (for each user)
// - 6 Strings (names and keys)
// - 3 Numbers (ids)
// Total: many heap allocations
// RawValue is a single allocation
let raw: Box<RawValue> = RawValue::from_string(
r#"{"users":[{"name":"Alice","id":1},{"name":"Bob","id":2},{"name":"Charlie","id":3}]}"#.to_string()
);
// RawValue is just a string reference
println!("RawValue size: {} bytes", size_of::<Box<RawValue>>());
println!("Value size: {} bytes", size_of::<Value>());
// Note: size_of only measures stack size, not heap allocations
// RawValue's heap: just the string
// Value's heap: all the structures above
}Value allocates heavily; RawValue stores only the raw string.
use serde_json::{Value, json};
use serde_json::value::RawValue;
use std::time::Instant;
fn passthrough_comparison() {
let json_data = r#"{
"id": 12345,
"name": "Test Item",
"description": "A longer description for testing",
"tags": ["tag1", "tag2", "tag3"],
"metadata": {
"created": "2024-01-01",
"modified": "2024-01-15",
"author": "system"
},
"values": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}"#;
let iterations = 10_000;
// Value: parse, then serialize
let start = Instant::now();
for _ in 0..iterations {
let value: Value = serde_json::from_str(json_data).unwrap();
let _output = serde_json::to_string(&value).unwrap();
}
let value_time = start.elapsed();
// RawValue: store raw, serialize directly
let start = Instant::now();
for _ in 0..iterations {
let raw: Box<RawValue> = serde_json::from_str(json_data).unwrap();
let _output = serde_json::to_string(&raw).unwrap();
}
let raw_time = start.elapsed();
println!("Value passthrough: {:?}", value_time);
println!("RawValue passthrough: {:?}", raw_time);
// RawValue is typically much faster for passthrough
}RawValue is significantly faster for passthrough scenarios.
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
// Common pattern: some fields known, others passed through
#[derive(Serialize, Deserialize)]
struct Envelope {
id: u64,
timestamp: i64,
// The payload is kept as raw JSON
#[serde(bound = "")]
payload: Box<RawValue>,
}
#[derive(Serialize, Deserialize)]
struct UserPayload {
name: String,
email: String,
}
fn envelope_pattern() {
let json = r#"{
"id": 123,
"timestamp": 1704067200,
"payload": {"name": "Alice", "email": "alice@example.com"}
}"#;
// Parse the envelope, but NOT the payload
let envelope: Envelope = serde_json::from_str(json).unwrap();
println!("ID: {}", envelope.id);
println!("Timestamp: {}", envelope.timestamp);
println!("Payload raw: {}", envelope.payload.get());
// Parse payload only when needed
let payload: UserPayload = serde_json::from_str(envelope.payload.get()).unwrap();
println!("Name: {}", payload.name);
println!("Email: {}", payload.email);
// Re-serialize without re-parsing payload
let output = serde_json::to_string(&envelope).unwrap();
println!("Output: {}", output);
}RawValue enables partial deserializationāparse what you need, pass through the rest.
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
#[derive(Serialize, Deserialize)]
struct Message {
msg_type: String,
version: u32,
#[serde(bound = "")]
data: Box<RawValue>,
}
fn nested_raw_value() {
let messages = vec![
r#"{"msg_type":"user","version":1,"data":{"name":"Alice"}}"#,
r#"{"msg_type":"order","version":2,"data":{"items":[1,2,3],"total":99.99}}"#,
r#"{"msg_type":"event","version":1,"data":{"type":"click","x":100,"y":200}}"#,
];
for msg_json in messages {
let msg: Message = serde_json::from_str(msg_json).unwrap();
match msg.msg_type.as_str() {
"user" => {
let user: UserEvent = serde_json::from_str(msg.data.get()).unwrap();
println!("User: {:?}", user);
}
"order" => {
let order: OrderEvent = serde_json::from_str(msg.data.get()).unwrap();
println!("Order: {:?}", order);
}
"event" => {
// Keep as raw if we don't need to parse
println!("Event data: {}", msg.data.get());
}
_ => println!("Unknown type: {}", msg.msg_type),
}
}
}
#[derive(Deserialize)]
struct UserEvent {
name: String,
}
#[derive(Deserialize)]
struct OrderEvent {
items: Vec<u32>,
total: f64,
}RawValue is ideal for message routing where payloads have different structures.
use serde_json::{Value, json};
fn dynamic_inspection() {
let value: Value = json!({
"users": [
{"id": 1, "name": "Alice", "active": true},
{"id": 2, "name": "Bob", "active": false},
{"id": 3, "name": "Charlie", "active": true}
],
"count": 3
});
// Value supports dynamic traversal
if let Some(users) = value.get("users").and_then(|v| v.as_array()) {
let active_count = users
.iter()
.filter(|user| user.get("active").and_then(|v| v.as_bool()).unwrap_or(false))
.count();
println!("Active users: {}", active_count);
}
// Modify based on conditions
let mut value = value;
if let Some(users) = value.get_mut("users").and_then(|v| v.as_array_mut()) {
for user in users {
if let Some(name) = user.get("name").and_then(|v| v.as_str()) {
if name == "Bob" {
user["active"] = json!(true);
}
}
}
}
// Add computed fields
value["active_count"] = json!(2);
println!("Modified: {}", serde_json::to_string_pretty(&value).unwrap());
}Value is necessary when you need to inspect and modify JSON dynamically.
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue;
use std::fs::File;
use std::io::{BufRead, BufReader};
#[derive(Deserialize)]
struct LogEntry {
timestamp: String,
level: String,
#[serde(bound = "")]
message: Box<RawValue>,
}
fn json_log_processing() {
// Simulate reading JSON log lines
let log_lines = vec![
r#"{"timestamp":"2024-01-15T10:00:00Z","level":"INFO","message":"Server started"}"#,
r#"{"timestamp":"2024-01-15T10:00:01Z","level":"DEBUG","message":{"event":"request","path":"/api/users"}}"#,
r#"{"timestamp":"2024-01-15T10:00:02Z","level":"ERROR","message":{"error":"connection failed","retry":3}}"#,
];
for line in log_lines {
// Parse only the envelope
let entry: LogEntry = serde_json::from_str(line).unwrap();
// Filter by level without parsing message
if entry.level == "ERROR" {
println!("[{}] ERROR: {}", entry.timestamp, entry.message.get());
}
}
// This is much faster than parsing each line into Value
// because we don't parse the message field at all
}RawValue is efficient for log processing where only some fields need parsing.
use serde_json::{Value, json};
use serde_json::value::RawValue;
use serde::{Serialize, Serializer};
fn serialization_differences() {
// Value: serializes from parsed structure
let value: Value = json!({"name": "Alice", "age": 30});
let value_json = serde_json::to_string(&value).unwrap();
println!("Value: {}", value_json);
// RawValue: serializes directly from string
let raw: Box<RawValue> = RawValue::from_string(
r#"{"name":"Alice","age":30}"#.to_string()
);
let raw_json = serde_json::to_string(&raw).unwrap();
println!("Raw: {}", raw_json);
// Output is the same, but RawValue doesn't re-parse
// Important: RawValue preserves original formatting
let pretty_raw: Box<RawValue> = RawValue::from_string(
r#"{
"name": "Alice",
"age": 30
}"#.to_string()
);
println!("Pretty preserved:\n{}", pretty_raw.get());
// This includes whitespace, key ordering, etc.
}RawValue preserves the original JSON formatting exactly.
use serde_json::{Value, json};
use serde_json::value::RawValue;
fn type_safety() {
// Value: guaranteed to be valid JSON after parsing
let value: Value = serde_json::from_str(r#"{"valid": true}"#).unwrap();
// value is now type-safe JSON
// RawValue: also validated on creation
let raw: Box<RawValue> = RawValue::from_string(
r#"{"valid": true}"#.to_string()
);
// This validates that it's valid JSON
// But RawValue can hold any valid JSON, no type constraints
let any_json: Box<RawValue> = RawValue::from_string(
r#"[1, 2, 3]"#.to_string()
);
// Dangerous: unsafe constructors
// RawValue::unsafe_from_string() bypasses validation
// Only use if you're CERTAIN the string is valid JSON
// Value gives you structural guarantees
if let Some(obj) = value.as_object() {
// obj is definitely a JSON object
for (key, val) in obj {
println!("{}: {}", key, val);
}
}
// RawValue requires re-parsing for structure
let parsed: Value = serde_json::from_str(raw.get()).unwrap();
// Now you have structural access
}Both types validate JSON, but Value provides structural access while RawValue requires re-parsing.
use serde_json::{Value, json};
use serde_json::value::RawValue;
use serde::{Deserialize, Serialize};
// Use Value when:
// 1. Need to inspect/query JSON dynamically
// 2. Need to modify JSON
// 3. Don't know structure at compile time
// 4. Building JSON from scratch
fn use_cases_for_value() {
// Dynamic queries
let api_response: Value = json!({
"data": {"users": [{"id": 1}, {"id": 2}]},
"meta": {"total": 2}
});
// Query nested paths
let user_ids: Vec<_> = api_response["data"]["users"]
.as_array()
.unwrap()
.iter()
.filter_map(|u| u.get("id").and_then(|v| v.as_u64()))
.collect();
// Modify structure
let mut response = api_response;
response["meta"]["filtered"] = json!(true);
}
// Use RawValue when:
// 1. Passthrough JSON without modification
// 2. Partial deserialization (envelope pattern)
// 3. Performance-critical paths
// 4. Preserving original formatting
#[derive(Serialize, Deserialize)]
struct ApiRequest {
id: String,
#[serde(bound = "")]
body: Box<RawValue>,
}
fn use_cases_for_raw_value() {
// API proxy: forward body without parsing
let request: ApiRequest = serde_json::from_str(r#"{
"id": "req-123",
"body": {"complex": {"nested": ["data", "here"]}}
}"#).unwrap();
// Use id, forward body directly
println!("Processing request: {}", request.id);
// body is still raw - can forward to another service
let forward_body = request.body.get();
// No parsing overhead for the complex nested structure
}Choose Value for manipulation, RawValue for passthrough.
use serde_json::{Value, json};
use serde_json::value::RawValue;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct FlexibleEnvelope {
msg_type: String,
// Keep as RawValue for efficiency
#[serde(bound = "")]
raw_data: Box<RawValue>,
}
impl FlexibleEnvelope {
// Parse into Value when needed
fn data_as_value(&self) -> Value {
serde_json::from_str(self.raw_data.get()).unwrap()
}
// Parse into typed struct when needed
fn data_as<T: for<'de> Deserialize<'de>>(&self) -> T {
serde_json::from_str(self.raw_data.get()).unwrap()
}
// Convert Value back to RawValue
fn from_value(msg_type: String, data: Value) -> Self {
let raw_data = RawValue::from_string(serde_json::to_string(&data).unwrap());
FlexibleEnvelope { msg_type, raw_data }
}
}
fn flexible_handling() {
let json = r#"{
"msg_type": "user_created",
"raw_data": {"user_id": 123, "email": "user@example.com"}
}"#;
let envelope: FlexibleEnvelope = serde_json::from_str(json).unwrap();
// Keep as raw for forwarding
println!("Forward: {}", envelope.raw_data.get());
// Parse when needed for processing
let value = envelope.data_as_value();
println!("User ID: {}", value["user_id"]);
// Parse into typed struct
#[derive(Deserialize)]
struct UserData {
user_id: u32,
email: String,
}
let typed: UserData = envelope.data_as();
println!("Typed: {:?}", typed);
}Convert between Value and RawValue as needed for different operations.
use serde_json::{Value, json};
use serde_json::value::RawValue;
use std::time::Instant;
fn performance_comparison() {
let large_json = r#"{
"users": [
{"id": 1, "name": "Alice", "email": "alice@example.com", "roles": ["admin", "user"]},
{"id": 2, "name": "Bob", "email": "bob@example.com", "roles": ["user"]},
{"id": 3, "name": "Charlie", "email": "charlie@example.com", "roles": ["user", "moderator"]}
],
"metadata": {
"version": "1.0",
"generated": "2024-01-15T10:00:00Z",
"count": 3
}
}"#;
let iterations = 10_000;
// Parse only
let start = Instant::now();
for _ in 0..iterations {
let _: Value = serde_json::from_str(large_json).unwrap();
}
let value_parse = start.elapsed();
let start = Instant::now();
for _ in 0..iterations {
let _: Box<RawValue> = serde_json::from_str(large_json).unwrap();
}
let raw_parse = start.elapsed();
println!("Parse - Value: {:?}", value_parse);
println!("Parse - RawValue: {:?}", raw_parse);
// Serialize
let value: Value = serde_json::from_str(large_json).unwrap();
let raw: Box<RawValue> = serde_json::from_str(large_json).unwrap();
let start = Instant::now();
for _ in 0..iterations {
let _ = serde_json::to_string(&value).unwrap();
}
let value_serialize = start.elapsed();
let start = Instant::now();
for _ in 0..iterations {
let _ = serde_json::to_string(&raw).unwrap();
}
let raw_serialize = start.elapsed();
println!("Serialize - Value: {:?}", value_serialize);
println!("Serialize - RawValue: {:?}", raw_serialize);
// RawValue typically wins significantly on both operations
}RawValue outperforms Value significantly for passthrough operations.
| Aspect | Value | RawValue |
|--------|---------|------------|
| Memory | Allocates for each element | Single string allocation |
| Parsing | Full parse into structures | Validates only, stores raw |
| Access | Direct traversal and indexing | Must parse to access content |
| Mutation | Fully mutable | Immutable, must re-create |
| Serialization | Reconstructs from structures | Outputs stored string |
| Type safety | Enum variants for types | Just valid JSON string |
| Use case | Inspect, modify, query | Passthrough, partial parse |
Value and RawValue serve complementary purposes:
Use Value when:
Use RawValue when:
Key insight: RawValue is essentially a zero-copy optimization that defers parsing until needed. It trades flexibility for performanceāyou cannot traverse or modify a RawValue without parsing it first. The envelope pattern (parsing outer structure while keeping inner payload as RawValue) is the canonical use case, enabling efficient routing, filtering, and forwarding of JSON messages without the overhead of fully parsing nested content. For APIs that receive and forward JSON, RawValue can dramatically reduce CPU and memory usage.