JSON parsing in Rust is safest when incoming text becomes a typed value at the edge of the program. Serde supplies the data-model traits, and serde_json applies those traits to JSON so application code can read fields instead of passing unchecked strings deeper into the codebase.
The usual binary-project setup is serde with the derive feature plus serde_json. A struct with #[derive(Deserialize)] defines the shape the program expects, and serde_json::from_str returns Result<T, serde_json::Error> instead of panicking when the JSON text is malformed or cannot deserialize into that shape.
An existing Cargo binary package is enough when JSON input belongs at a clear boundary, such as an API response, configuration file, or test fixture. A small service-configuration payload keeps the success and failure paths visible because valid text prints typed fields, while a trailing comma returns a parser error.
The directory should contain the Cargo.toml file for the package. Create a package first if needed.
Related: How to create a Rust project with Cargo
$ cargo add serde --features derive
The derive feature provides #[derive(Deserialize)] for structs and enums in the package.
Related: How to add a Rust dependency with Cargo
$ cargo add serde_json
serde defines the traits, while serde_json reads and writes JSON using those traits.
use serde::Deserialize; #[derive(Deserialize)] struct ServiceConfig { service: String, retries: u8, enabled: bool, } fn parse_config(input: &str) -> Result<ServiceConfig, serde_json::Error> { serde_json::from_str(input) } fn print_config(input: &str) { match parse_config(input) { Ok(config) => { println!( "loaded service={} retries={} enabled={}", config.service, config.retries, config.enabled ); } Err(err) => println!("invalid JSON: {err}"), } } fn main() { let valid_json = r#"{"service":"billing","retries":3,"enabled":true}"#; let invalid_json = r#"{"service":"billing","retries":3,"enabled":true,}"#; print_config(valid_json); print_config(invalid_json); }
The Rust field names must match the JSON object keys unless a serde field attribute, such as #[serde(rename = "jsonName")], maps a different key.
$ cargo run --quiet loaded service=billing retries=3 enabled=true invalid JSON: trailing comma at line 1 column 49
The first output line comes from the valid ServiceConfig value. The second line comes from the Err branch returned by serde_json::from_str.
Tool: JSON Validator