> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/tokio-rs/axum/llms.txt
> Use this file to discover all available pages before exploring further.

# JSON

> Work with JSON request bodies and responses in Axum

The `Json` extractor and response type makes it easy to work with JSON data in Axum applications.

## Extracting JSON request bodies

Use the `Json` extractor to deserialize JSON from request bodies:

### Basic JSON extraction

<Steps>
  <Step title="Define your data structure">
    Create a struct that implements `Deserialize`:

    ```rust theme={null}
    use serde::Deserialize;

    #[derive(Deserialize)]
    struct CreateUser {
        username: String,
        email: String,
        age: u32,
    }
    ```
  </Step>

  <Step title="Use the Json extractor">
    Extract JSON in your handler:

    ```rust theme={null}
    use axum::Json;

    async fn create_user(Json(payload): Json<CreateUser>) {
        // payload is a CreateUser struct
        println!("Creating user: {}", payload.username);
    }
    ```
  </Step>

  <Step title="Set up the route">
    ```rust theme={null}
    use axum::{routing::post, Router};

    let app = Router::new()
        .route("/users", post(create_user));
    ```
  </Step>
</Steps>

<Note>
  The `Json` extractor requires the request to have a `Content-Type: application/json` header. Requests without this header will be rejected with a 415 Unsupported Media Type error.
</Note>

## JSON responses

Return `Json` to automatically serialize responses:

```rust theme={null}
use axum::Json;
use serde::Serialize;
use uuid::Uuid;

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

async fn get_user() -> Json<User> {
    let user = User {
        id: Uuid::new_v4(),
        username: "alice".to_string(),
        email: "alice@example.com".to_string(),
    };
    
    Json(user)
}
```

This automatically sets the `Content-Type: application/json` header and serializes the struct.

## Working with generic JSON

Use `serde_json::Value` for dynamic JSON:

<CodeGroup>
  ```rust Accepting any JSON theme={null}
  use serde_json::Value;

  async fn handle_json(Json(payload): Json<Value>) -> String {
      // payload can be any valid JSON
      format!("Received: {}", payload)
  }
  ```

  ```rust Building JSON responses theme={null}
  use serde_json::json;

  async fn api_response() -> Json<Value> {
      Json(json!({
          "success": true,
          "data": {
              "items": [1, 2, 3],
              "total": 3
          },
          "message": "Operation completed"
      }))
  }
  ```

  ```rust Manipulating JSON theme={null}
  async fn transform_json(Json(mut payload): Json<Value>) -> Json<Value> {
      // Add a timestamp
      if let Some(obj) = payload.as_object_mut() {
          obj.insert(
              "timestamp".to_string(),
              json!(chrono::Utc::now().to_rfc3339()),
          );
      }
      Json(payload)
  }
  ```
</CodeGroup>

## Complete CRUD example

Here's a full REST API example:

```rust theme={null}
use axum::{
    extract::Path,
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post, put, delete},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

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

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

#[derive(Deserialize)]
struct UpdateUser {
    username: Option<String>,
    email: Option<String>,
}

// Create
async fn create_user(Json(payload): Json<CreateUser>) -> (StatusCode, Json<User>) {
    let user = User {
        id: Uuid::new_v4(),
        username: payload.username,
        email: payload.email,
    };
    
    (StatusCode::CREATED, Json(user))
}

// Read one
async fn get_user(Path(user_id): Path<Uuid>) -> Json<User> {
    // In a real app, fetch from database
    Json(User {
        id: user_id,
        username: "alice".to_string(),
        email: "alice@example.com".to_string(),
    })
}

// Read all
async fn list_users() -> Json<Vec<User>> {
    // In a real app, fetch from database
    Json(vec![
        User {
            id: Uuid::new_v4(),
            username: "alice".to_string(),
            email: "alice@example.com".to_string(),
        },
    ])
}

// Update
async fn update_user(
    Path(user_id): Path<Uuid>,
    Json(payload): Json<UpdateUser>,
) -> Json<User> {
    // In a real app, update in database
    Json(User {
        id: user_id,
        username: payload.username.unwrap_or_else(|| "alice".to_string()),
        email: payload.email.unwrap_or_else(|| "alice@example.com".to_string()),
    })
}

// Delete
async fn delete_user(Path(user_id): Path<Uuid>) -> StatusCode {
    // In a real app, delete from database
    StatusCode::NO_CONTENT
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/users", get(list_users).post(create_user))
        .route("/users/:id", get(get_user).put(update_user).delete(delete_user));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await;
}
```

## Error handling

Handle JSON deserialization errors:

```rust theme={null}
use axum::{
    extract::rejection::JsonRejection,
    http::StatusCode,
    response::{IntoResponse, Response},
};

#[derive(Deserialize)]
struct Input {
    value: i32,
}

async fn handler(
    payload: Result<Json<Input>, JsonRejection>,
) -> Response {
    match payload {
        Ok(Json(input)) => {
            format!("Received: {}", input.value).into_response()
        }
        Err(err) => {
            // Customize error response
            (
                StatusCode::BAD_REQUEST,
                format!("Invalid JSON: {err}"),
            ).into_response()
        }
    }
}
```

### JSON error types

The `Json` extractor can fail with these errors:

<Accordion title="JSON rejection types">
  * **`MissingJsonContentType`**: Request missing `Content-Type: application/json` header (HTTP 415)
  * **`JsonDataError`**: JSON is valid but doesn't match the target type (HTTP 422)
  * **`JsonSyntaxError`**: Invalid JSON syntax (HTTP 400)
  * **`BytesRejection`**: Failed to buffer request body
</Accordion>

## Content type variants

The `Json` extractor accepts various JSON content types:

```rust theme={null}
// All these content types are accepted:
// - application/json
// - application/json; charset=utf-8
// - application/cloudevents+json  (any +json suffix)

async fn flexible_json(Json(data): Json<Value>) -> String {
    format!("Received: {data}")
}
```

## Response customization

Customize JSON responses with status codes and headers:

<CodeGroup>
  ```rust With status code theme={null}
  use axum::http::StatusCode;

  async fn create_item(Json(item): Json<NewItem>) -> (StatusCode, Json<Item>) {
      let created_item = save_item(item).await;
      (StatusCode::CREATED, Json(created_item))
  }
  ```

  ```rust With headers theme={null}
  use axum::http::header;

  async fn get_item() -> ([(header::HeaderName, &'static str); 1], Json<Item>) {
      (
          [(header::CACHE_CONTROL, "max-age=3600")],
          Json(get_cached_item().await),
      )
  }
  ```

  ```rust Full response control theme={null}
  use axum::{
      response::{IntoResponse, Response},
      http::{StatusCode, header},
  };

  async fn custom_response() -> Response {
      let data = json!({"status": "ok"});
      
      (   
          StatusCode::OK,
          [(header::CONTENT_TYPE, "application/json")],
          Json(data)
      ).into_response()
  }
  ```
</CodeGroup>

## Performance optimization

For large JSON payloads, consider streaming:

```rust theme={null}
use axum::{
    body::Bytes,
    extract::Request,
    http::StatusCode,
};

async fn large_json_handler(request: Request) -> Result<String, StatusCode> {
    let bytes = Bytes::from_request(request, &())
        .await
        .map_err(|_| StatusCode::BAD_REQUEST)?;
    
    // Deserialize from bytes directly
    let data: MyLargeStruct = Json::<MyLargeStruct>::from_bytes(&bytes)
        .map_err(|_| StatusCode::UNPROCESSABLE_ENTITY)?
        .0;
    
    Ok(format!("Processed {} items", data.items.len()))
}
```

## Testing JSON handlers

```rust theme={null}
#[cfg(test)]
mod tests {
    use super::*;
    use axum::{
        body::Body,
        http::{self, Request, StatusCode},
    };
    use http_body_util::BodyExt;
    use serde_json::json;
    use tower::ServiceExt;

    #[tokio::test]
    async fn test_create_user() {
        let app = Router::new().route("/users", post(create_user));

        let response = app
            .oneshot(
                Request::post("/users")
                    .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
                    .body(Body::from(
                        serde_json::to_vec(&json!({
                            "username": "alice",
                            "email": "alice@example.com"
                        }))
                        .unwrap(),
                    ))
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::CREATED);

        let body = response.into_body().collect().await.unwrap().to_bytes();
        let user: User = serde_json::from_slice(&body).unwrap();
        assert_eq!(user.username, "alice");
    }

    #[tokio::test]
    async fn test_invalid_json() {
        let app = Router::new().route("/users", post(create_user));

        let response = app
            .oneshot(
                Request::post("/users")
                    .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
                    .body(Body::from("{invalid json}"))
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
    }
}
```

## Best practices

<Warning>
  The `Json` extractor must be the last extractor in a handler if you have multiple extractors, since it consumes the request body.
</Warning>

<Info>
  Always use strongly-typed structs instead of `Value` when possible. This provides better type safety and clearer API documentation.
</Info>

<Tip>
  Use `#[serde(rename_all = "camelCase")]` on your structs to automatically convert between Rust's snake\_case and JSON's camelCase conventions.
</Tip>
