Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
166: Working with serde_json Value
Imagine you're sorting through a big box of mixed mail. You've got letters, postcards, thick envelopes with bills, and maybe a few glossy brochures. Before you can actually do anything with a piece of mail, you have to look at it and figure out what it is. Is this a letter? If so, I can read the text. Is this a bill? If so, I can look for the "Amount Due" field. You can't just treat everything as a bill, because trying to find an "Amount Due" on a postcard doesn't make sense.
That's exactly how serde_json::Value works. Up until now, we've been using strongly typed structs to deserialize JSON. That's great when you know exactly what the API is sending. But in the real world, you'll often run into "schemaless" or dynamic JSON—where the keys might change, or the value could be a string one time and a number the next. In those cases, we use Value, which is essentially a giant enum that says: "This is either an Object, an Array, a String, a Number, a Boolean, or Null."
When you can't trust the schema
I've spent a lot of time working with legacy APIs where the documentation is a lie. You'll see a field called metadata that is sometimes a simple string, sometimes a complex object, and sometimes just null. If you try to map that to a strict Rust struct, your program will crash (or rather, fail to deserialize) the moment the API sends something unexpected.
By deserializing into serde_json::Value, you're telling Rust: "I don't know what this is yet. Just hold onto the data, and I'll figure it out at runtime."
use serde_json::Value;
fn main() {
let data = r#"
{
"name": "Project Phoenix",
"version": 2,
"tags": ["rust", "json", "dynamic"],
"settings": {
"retries": 3,
"enabled": true
}
}
"#;
// We parse into Value instead of a custom struct
let v: Value = serde_json::from_str(data).unwrap();
// Now we can access fields dynamically
println!("Project: {}", v["name"]);
}
Peeking inside the Value enum
The Value type implements the Index trait, which is why I could use v["name"] above. It feels like JavaScript, but there's a catch: if the key doesn't exist, it returns a Value::Null. It won't panic immediately, but it might cause issues later if you assume the data is there.
If you want to actually use the data as a Rust type (like a &str or an i64), you have to use the as_... methods. These return an Option, forcing you to handle the case where the data isn't what you thought it was.
// Accessing a string safely
if let Some(name) = v["name"].as_str() {
println!("The name is {}", name);
}
// Accessing a nested number
if let Some(retries) = v["settings"]["retries"].as_i64() {
println!("Retries set to: {}", retries);
}
// Handling arrays
if let Some(tags) = v["tags"].as_array() {
for tag in tags {
println!("Tag: {}", tag);
}
}
The danger of the panic-prone index
Here is a bit of a warning. While v["key"] is convenient, it's a bit "loose." If you're writing production code and you need to be absolutely sure a key exists before you try to manipulate it, I recommend using .get(). .get() returns an Option<&Value>, which is much cleaner when you're chaining lookups.
I usually prefer .get() over [] because it makes the possibility of failure explicit. In a large codebase, seeing an Option tells the next developer (which might be you in six months) that this piece of JSON isn't guaranteed to be there.
// Instead of this (which returns Value::Null if missing):
let val = &v["missing_key"];
// Do this (which returns None if missing):
if let Some(val) = v.get("missing_key") {
println!("Found it: {}", val);
} else {
println!("Key was missing entirely.");
}
📋 Practical Task
Build a Dynamic API Response Filter
You are building a tool that processes responses from a chaotic legacy API. The API returns a JSON object where some fields are consistent, but others are nested in a metadata object that changes structure depending on the request.
Your Task: Write a function extract_priority(json_data: &str) -> Option<i64> that does the following:
- Parses the input string into a
serde_json::Value. - Checks if there is a top-level field called
"priority". If it exists and is a number, return it. - If
"priority"is not at the top level, look inside a field called"metadata". If"metadata"is an object and contains a field called"priority"that is a number, return that. - If neither exists or the value is not a number, return
None.
Test your function with these three cases:
{"priority": 10, "name": "Task A"}→ Should returnSome(10){"name": "Task B", "metadata": {"priority": 5, "source": "web"}}→ Should returnSome(5){"name": "Task C", "metadata": {"tags": ["urgent"]}}→ Should returnNone
There are no comments for now.