Loading pageā¦
Rust walkthroughs
Loading pageā¦
reqwest::Response::json and text for deserializing response bodies?Response::text() consumes the response body and returns it as a String, automatically decoding the bytes according to the charset specified in the Content-Type header (defaulting to UTF-8 if unspecified). Response::json() consumes the response body and directly deserializes it into any type implementing serde::Deserialize, returning an error if the body isn't valid JSON or doesn't match the target type. The key distinction is that text() gives you the raw string representation for further processing, while json() performs immediate deserializationājson() is essentially text() followed by serde_json::from_str(), but with optimized memory handling since it can deserialize directly from the response body bytes without an intermediate string allocation when the charset is UTF-8.
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
// Using text() - get raw string
let text = response.text().await?;
println!("Raw text: {}", text);
// You would then parse manually if needed:
// let user: User = serde_json::from_str(&text)?;
// Using json() - deserialize directly
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let user: User = response.json().await?;
println!("User: {:?}", user);
Ok(())
}text() returns the raw body as a string; json() deserializes directly into your type.
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Post {
id: u32,
title: String,
body: String,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
// text() returns the body as a String
let response = reqwest::get("https://jsonplaceholder.typicode.com/posts/1").await?;
let text: String = response.text().await?;
println!("text() returns: {}", text.len());
// The text is the raw JSON string
println!("First 100 chars: {}...", &text[..100.min(text.len())]);
// json() deserializes directly into your type
let response = reqwest::get("https://jsonplaceholder.typicode.com/posts/1").await?;
let post: Post = response.json().await?;
println!("\njson() returns: {:?}", post);
// The response body is consumed and deserialized in one step
Ok(())
}text() gives you the raw string for any processing; json() handles deserialization for you.
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: u64,
name: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// json() can fail in multiple ways:
// 1. Network error (same as text())
// 2. Invalid JSON syntax
// 3. JSON doesn't match the target type
// Example: JSON that doesn't match the struct
let response = reqwest::get("https://jsonplaceholder.typicode.com/posts/1").await?;
// This will fail because the response has "title" and "body",
// but our User struct expects "name"
let result: Result<User, _> = response.json().await;
match result {
Ok(user) => println!("User: {:?}", user),
Err(e) => {
// Error::decode means JSON parsing failed
// or the structure didn't match
println!("Deserialization error: {}", e);
if e.is_decode() {
println!(" - Invalid JSON or type mismatch");
}
}
}
// With text(), you get the raw text and can handle parsing yourself
let response = reqwest::get("https://jsonplaceholder.typicode.com/posts/1").await?;
let text = response.text().await?;
// You can inspect the text before parsing
println!("Got text, attempting manual parse...");
match serde_json::from_str::<User>(&text) {
Ok(user) => println!("User: {:?}", user),
Err(e) => {
// You still have the original text available
println!("Parse error: {}", e);
println!("Original text was: {}...", &text[..50.min(text.len())]);
}
}
Ok(())
}json() combines network and deserialization errors; text() lets you separate concerns.
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct ApiResponse<T> {
data: T,
meta: Option<serde_json::Value>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// USE CASE 1: Response might not be JSON
let response = reqwest::get("https://example.com").await?;
let text = response.text().await?;
// HTML or other non-JSON content
if text.starts_with("<!DOCTYPE") || text.starts_with("<html") {
println!("Got HTML response");
}
// USE CASE 2: Need to inspect before parsing
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let text = response.text().await?;
// Check content before parsing
if text.is_empty() {
println!("Empty response");
} else if text.starts_with('{') {
let user: serde_json::Value = serde_json::from_str(&text)?;
println!("JSON object: {}", user);
} else if text.starts_with('[') {
let users: Vec<serde_json::Value> = serde_json::from_str(&text)?;
println!("JSON array with {} items", users.len());
}
// USE CASE 3: Multiple parsing attempts
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let text = response.text().await?;
// Try parsing as different types
#[derive(Debug, Deserialize)]
struct UserV1 { id: u32, name: String }
#[derive(Debug, Deserialize)]
struct UserV2 { id: u32, name: String, email: String }
if let Ok(user) = serde_json::from_str::<UserV2>(&text) {
println!("Parsed as V2: {:?}", user);
} else if let Ok(user) = serde_json::from_str::<UserV1>(&text) {
println!("Parsed as V1: {:?}", user);
}
// USE CASE 4: Logging and debugging
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let text = response.text().await?;
println!("Raw response for debugging: {}", text);
let user: serde_json::Value = serde_json::from_str(&text)?;
Ok(())
}Use text() when you need raw access, multiple parse attempts, or non-JSON content.
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
// USE CASE 1: Known JSON structure, direct deserialization
let user: User = reqwest::get("https://jsonplaceholder.typicode.com/users/1")
.await?
.json()
.await?;
println!("User: {:?}", user);
// USE CASE 2: Collection of items
let users: Vec<User> = reqwest::get("https://jsonplaceholder.typicode.com/users")
.await?
.json()
.await?;
println!("Got {} users", users.len());
// USE CASE 3: Nested structures
#[derive(Debug, Deserialize)]
struct Post {
id: u32,
title: String,
user: User,
}
// USE CASE 4: Generic API responses
#[derive(Debug, Deserialize)]
struct ApiResponse<T> {
data: T,
status: String,
}
// Works with generic types
let response: ApiResponse<User> = reqwest::get("https://jsonplaceholder.typicode.com/users/1")
.await?
.json()
.await?;
// USE CASE 5: Simple, clean code
// One-liner for simple cases
let user: User = reqwest::get("https://jsonplaceholder.typicode.com/users/1")
.await?
.json()
.await?;
Ok(())
}Use json() when you know the structure and want clean, direct deserialization.
use reqwest::Error;
#[tokio::main]
async fn main() -> Result<(), Error> {
// text() automatically handles charset decoding
// based on Content-Type header
// If Content-Type: text/plain; charset=utf-8
// -> Decodes as UTF-8
// If Content-Type: text/plain; charset=iso-8859-1
// -> Decodes as ISO-8859-1
// If no charset specified
// -> Defaults to UTF-8
// If Content-Type: application/json
// -> Decodes as UTF-8 (JSON is always UTF-8)
let response = reqwest::get("https://example.com").await?;
// Check the content type before getting text
let content_type = response.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
println!("Content-Type: {}", content_type);
let text = response.text().await?;
println!("Decoded text length: {}", text.len());
// json() doesn't do charset conversion
// JSON is defined as UTF-8, so json() expects UTF-8 bytes
// Non-UTF-8 responses will cause json() to fail
Ok(())
}text() handles charset decoding; json() assumes UTF-8 as per JSON specification.
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct LargeResponse {
items: Vec<Item>,
}
#[derive(Debug, Deserialize)]
struct Item {
id: u64,
name: String,
description: String,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
// json() can be more memory efficient
// It can deserialize directly from the response body bytes
// without creating an intermediate String
// Conceptually:
// json() = serde_json::from_slice(&response_bytes)
// text() = String::from_utf8(response_bytes) then serde_json::from_str(&text)
// For large responses, json() avoids the intermediate string
// But if you need both the raw text AND the parsed value:
// Use text() and parse separately
let response = reqwest::get("https://jsonplaceholder.typicode.com/users").await?;
// If you need both:
// Option A: text() then parse
let text = response.text().await?;
let users: Vec<serde_json::Value> = serde_json::from_str(&text)?;
// Now you have both 'text' and 'users'
// Option B: json() with Value type
let response = reqwest::get("https://jsonplaceholder.typicode.com/users").await?;
let value: serde_json::Value = response.json().await?;
// You can serialize Value back to string if needed
let text = serde_json::to_string(&value)?;
// Option A is usually more efficient if you need the raw text
Ok(())
}json() can deserialize directly from bytes; text() creates an intermediate string.
use reqwest::{Error, Response, StatusCode};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: u64,
name: String,
}
#[derive(Debug, Deserialize)]
struct ErrorResponse {
message: String,
code: u32,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/9999").await?;
// Check status before deserializing
let status = response.status();
if status.is_success() {
let user: User = response.json().await?;
println!("User: {:?}", user);
} else {
// For error responses, structure might be different
// Use text() to inspect or generic Value type
let error_text = response.text().await?;
println!("Error response ({}): {}", status, error_text);
// Try to parse as error response
if let Ok(error) = serde_json::from_str::<ErrorResponse>(&error_text) {
println!("Error code: {}, message: {}", error.code, error.message);
}
}
// Alternative: Use a custom error handling approach
async fn parse_response<T: for<'de> Deserialize<'de>>(
response: Response
) -> Result<T, Box<dyn std::error::Error>> {
let status = response.status();
let text = response.text().await?;
if status.is_success() {
Ok(serde_json::from_str(&text)?)
} else {
Err(format!("HTTP {}: {}", status, text).into())
}
}
Ok(())
}Check HTTP status before deserializing; use text() for error responses with unknown structure.
use reqwest::Error;
use serde::Deserialize;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://example.com/api").await?;
// Check content type to decide how to handle response
let content_type = response.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if content_type.contains("application/json") {
// Use json() for JSON content
let data: serde_json::Value = response.json().await?;
println!("JSON: {}", data);
} else if content_type.contains("text/") {
// Use text() for text content
let text = response.text().await?;
println!("Text: {}", text);
} else if content_type.contains("application/octet-stream") {
// Use bytes() for binary content
let bytes = response.bytes().await?;
println!("Binary: {} bytes", bytes.len());
} else {
// Default to text for unknown types
let text = response.text().await?;
println!("Unknown content type: {}", content_type);
println!("Body: {}", text);
}
Ok(())
}Check Content-Type header to choose between json(), text(), or bytes().
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: u64,
name: String,
}
#[derive(Debug)]
enum ParseError {
Reqwest(Error),
Json(serde_json::Error),
}
impl From<Error> for ParseError {
fn from(e: Error) -> Self {
ParseError::Reqwest(e)
}
}
impl From<serde_json::Error> for ParseError {
fn from(e: serde_json::Error) -> Self {
ParseError::Json(e)
}
}
#[tokio::main]
async fn main() -> Result<(), ParseError> {
// Strategy: Use text() for debugging, then parse
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let text = response.text().await?;
// Log for debugging
println!("Response body: {}", text);
// Parse with better error context
let user: User = serde_json::from_str(&text).map_err(|e| {
println!("Failed to parse JSON: {}", e);
println!("Original response: {}", text);
e
})?;
println!("User: {:?}", user);
// Alternative: Try json() first, fall back to text() on error
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
// This doesn't work because json() consumes the response
// You can't fall back to text() after json() fails
// Solution: Use bytes() to get raw bytes, then try both
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let bytes = response.bytes().await?;
// Try JSON parse first
match serde_json::from_slice::<User>(&bytes) {
Ok(user) => println!("Parsed as JSON: {:?}", user),
Err(_) => {
// Fall back to text
let text = String::from_utf8_lossy(&bytes);
println!("Not valid JSON User: {}", text);
}
}
Ok(())
}Use bytes() when you need to try multiple approaches with the same response body.
use reqwest::Error;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
id: u64,
name: String,
}
#[tokio::main]
async fn main() -> Result<(), Error> {
// IMPORTANT: Response body can only be consumed once
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
// This consumes the response body
let user: User = response.json().await?;
// This would panic or return an error:
// let text = response.text().await?; // ERROR: body already consumed
// Similarly:
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let text = response.text().await?;
// This would fail:
// let user: User = response.json().await?; // ERROR: body already consumed
// If you need both, use bytes() and process manually
let response = reqwest::get("https://jsonplaceholder.typicode.com/users/1").await?;
let bytes = response.bytes().await?;
// Now you have the bytes, can process multiple times
let text = String::from_utf8_lossy(&bytes);
println!("Text: {}", text);
let user: User = serde_json::from_slice(&bytes)?;
println!("User: {:?}", user);
Ok(())
}Response body is consumed by text(), json(), or bytes(); you can only call one.
Method comparison:
| Method | Returns | Use Case |
|--------|---------|----------|
| text() | String | Raw string access, non-JSON content, debugging |
| json() | T: Deserialize | Direct deserialization, known JSON structure |
| bytes() | Bytes | Binary data, multiple processing attempts |
When to use each method:
| Scenario | Recommended Method |
|----------|-------------------|
| Known JSON structure | json() |
| Need raw text for logging | text() |
| Non-JSON response (HTML, plain text) | text() |
| Binary data (images, files) | bytes() |
| Unknown structure, might not be JSON | text() |
| Multiple parse attempts | text() or bytes() |
| Debugging deserialization errors | text() + manual parse |
| Large JSON responses | json() (avoids intermediate string) |
| Need both raw and parsed | bytes() + from_slice |
Error types:
| Method | Error Sources |
|--------|---------------|
| text() | Network error, charset decoding error |
| json() | Network error, charset error, JSON parse error, type mismatch |
Key insight: The choice between json() and text() is about separation of concerns versus convenience. json() combines HTTP response handling with deserializationāconvenient when you know the structure and trust the server. text() gives you control by separating the steps: first get the raw response, then decide how to parse it. This separation is valuable when handling potentially malformed responses, varying content types, or when you need to log raw responses for debugging. Internally, json() is optimized to deserialize directly from the response body bytes when the charset is UTF-8, avoiding an intermediate string allocationāthis makes it more efficient than text() followed by manual parsing for the common case of UTF-8 JSON. However, if you need both the raw text and the parsed value, using text() once is more efficient than calling json() and re-serializing the result. For maximum flexibility with large responses, bytes() gives you the raw bytes which you can then convert to text or parse as JSON as needed.