Step 5 of 6
Dynamic JSON with Value and json!
Sometimes you don't want a struct: you only need one field from a big document, the shape varies, or you're building a quick response. For that, serde_json has serde_json::Value, an enum that can hold any JSON:
enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
use serde_json::{json, Value};
fn main() {
let text = r#"{"user": {"name": "Ferris", "tags": ["crab", "admin"]}, "visits": 12}"#;
let v: Value = serde_json::from_str(text).unwrap();
println!("{}", v["user"]["name"]);
println!("{:?}", v["user"]["name"].as_str());
println!("{}", v["visits"].as_u64().unwrap_or(0) + 1);
println!("{}", v["user"]["email"]);
println!("{}", v["user"]["tags"][1]);
let reply = json!({ "ok": true, "visits": v["visits"], "id": 7 });
println!("{reply}");
}
"Ferris"
Some("Ferris")
13
null
"admin"
{"id":7,"ok":true,"visits":12}
Piece by piece:
v["user"]["name"]indexes into objects by key and arrays by position. A missing key or index givesValue::Nullinstead of panicking, so you can chain lookups safely.- Printing a
Valueprints JSON: a string comes out with its quotes,"Ferris". To get the Rust text, use the accessors:as_str(),as_u64(),as_i64(),as_f64(),as_bool(),as_array(),as_object(). Each returns anOption:Noneif the value is missing or of another type. v.get("user")is theOption-returning lookup:Nonewhen the key isn't there.- The
json!macro builds aValuewith JSON syntax, and you can put Rust expressions inside (v["visits"]here, or any variable that implementsSerialize). - Object keys come out sorted (
id,ok,visits): serde_json'sMapis aBTreeMapunless you enable itspreserve_orderfeature. Sorted is deterministic, which tests like.
Value is an ordinary enum, so you can match on it:
use serde_json::Value;
fn describe(v: &Value) -> String {
match v {
Value::Null => String::from("nothing"),
Value::Bool(b) => format!("yes/no: {b}"),
Value::Number(n) => format!("number {n}"),
Value::String(s) => format!("text {s}"),
Value::Array(items) => format!("list of {}", items.len()),
Value::Object(map) => format!("object with {} keys", map.len()),
}
}
fn main() {
let v: Value = serde_json::from_str(r#"[null, true, 2.5, "hi", [1, 2], {"a": 1}]"#).unwrap();
for item in v.as_array().unwrap() {
println!("{}", describe(item));
}
}
nothing
yes/no: true
number 2.5
text hi
list of 2
object with 1 keys
You can also convert between the two worlds: serde_json::to_value(&my_struct) and serde_json::from_value::<MyStruct>(value).
When to use which
Prefer structs: the compiler checks every field access, and a wrong shape is reported in one place, at parse time. Use Value for genuinely dynamic data (arbitrary user metadata, proxying JSON you don't own, picking one field out of a huge response) and for tests, where json! makes expected values easy to write.
Common mistakes
- Printing
v["name"]and getting quotes. Usev["name"].as_str(). unwrap()on accessors for data you don't control:v["age"].as_u64().unwrap()panics if age is missing or"12"(a string). Useunwrap_or,if let, or?on anOption.- Typos are silent.
v["usr"]["name"]is justNull, no error. That's the price of skipping structs.
Your task
Write fn extract_emails(json: &str) -> Vec<String>. The input looks like {"users": [{"email": "[email protected]"}, {"name": "no email"}, ...]}. Return every email that is a string, in order, skipping users without one. If the input isn't valid JSON, or users isn't an array, return an empty Vec.
Challenge 2
Build a response with json!. Write fn stats(scores: &[i64]) -> Value returning {"count": <number of scores>, "max": <largest score>, "min": <smallest score>}, where max and min are null when the slice is empty. (Hint: an Option inside json! becomes the value or null.)
Challenge 3
Write a program that reads one JSON object from standard input and prints each top-level key with the kind of its value (null, bool, number, string, array or object), one per line as key: kind, in sorted key order. If the input isn't a JSON object, print not an object.
Previous: Enums in JSON Next: Bad input, validation and other formats