Skip to main content
JSON extractor and response type. When used as an extractor, it deserializes request bodies into types that implement serde::DeserializeOwned. When used as a response, it serializes types that implement serde::Serialize to JSON.

Type signature

pub struct Json<T>(pub T);

As an extractor

The Json extractor consumes the request body and deserializes it as JSON.
Since parsing JSON requires consuming the request body, the Json extractor must be last if there are multiple extractors in a handler. See the order of extractors.

Basic usage

use axum::{
    extract::Json,
    routing::post,
    Router,
};
use serde::Deserialize;

#[derive(Deserialize)]
struct CreateUser {
    email: String,
    password: String,
}

async fn create_user(Json(payload): Json<CreateUser>) {
    // payload is a CreateUser
    let email = payload.email;
    let password = payload.password;
    // ...
}

let app = Router::new().route("/users", post(create_user));

Content-Type validation

The request must have a Content-Type: application/json header (or similar like application/problem+json). Requests without this header will be rejected with 415 Unsupported Media Type.
// Valid content types:
// - application/json
// - application/json; charset=utf-8
// - application/cloudevents+json
// - application/*+json (any JSON-based media type)

As a response

When returned from a handler, Json serializes the value to JSON and sets the Content-Type: application/json header automatically.
use axum::{
    extract::Path,
    routing::get,
    Router,
    Json,
};
use serde::Serialize;
use uuid::Uuid;

#[derive(Serialize)]
struct User {
    id: Uuid,
    username: String,
}

async fn get_user(Path(user_id): Path<Uuid>) -> Json<User> {
    let user = find_user(user_id).await;
    Json(user)
}

async fn find_user(user_id: Uuid) -> User {
    // ...
    # todo!()
}

let app = Router::new().route("/users/{id}", get(get_user));

Error handling

Extractor rejections

MissingJsonContentType
415
The request doesn’t have a Content-Type: application/json header.
JsonSyntaxError
400
The body doesn’t contain syntactically valid JSON.
JsonDataError
422
The body contains syntactically valid JSON, but it couldn’t be deserialized into the target type.
FailedToBufferBody
varies
Buffering the request body failed.

Example error responses

// Missing content-type header:
// Status: 415 Unsupported Media Type

// Invalid JSON syntax:
// Status: 400 Bad Request
// Body: "Failed to parse the request body as JSON: EOF while parsing an object at line 1 column 1"

// Invalid data:
// Status: 422 Unprocessable Entity  
// Body: "Failed to deserialize the JSON body into the target type: missing field `email` at line 1 column 25"

Response serialization errors

If serialization fails when using Json as a response, a 500 Internal Server Error will be returned with the error message as the body.

Parsing from bytes

You can construct a Json<T> from bytes directly:
use axum::Json;
use serde::Deserialize;

#[derive(Deserialize)]
struct Data {
    value: String,
}

let bytes = br#"{"value": "hello"}"#;
let Json(data): Json<Data> = Json::from_bytes(bytes).unwrap();
assert_eq!(data.value, "hello");

Optional JSON

Use Option<Json<T>> to make JSON extraction optional based on the presence of a Content-Type header:
async fn handler(json: Option<Json<Data>>) {
    match json {
        Some(Json(data)) => {
            // Request had JSON content
        }
        None => {
            // No Content-Type header present
        }
    }
}