axum/
json.rs

1use crate::extract::Request;
2use crate::extract::{rejection::*, FromRequest};
3use axum_core::response::{IntoResponse, Response};
4use bytes::{BufMut, Bytes, BytesMut};
5use http::{
6    header::{self, HeaderMap, HeaderValue},
7    StatusCode,
8};
9use serde::{de::DeserializeOwned, Serialize};
10
11/// JSON Extractor / Response.
12///
13/// When used as an extractor, it can deserialize request bodies into some type that
14/// implements [`serde::de::DeserializeOwned`]. The request will be rejected (and a [`JsonRejection`] will
15/// be returned) if:
16///
17/// - The request doesn't have a `Content-Type: application/json` (or similar) header.
18/// - The body doesn't contain syntactically valid JSON.
19/// - The body contains syntactically valid JSON, but it couldn't be deserialized into the target type.
20/// - Buffering the request body fails.
21///
22/// ⚠️ Since parsing JSON requires consuming the request body, the `Json` extractor must be
23/// *last* if there are multiple extractors in a handler.
24/// See ["the order of extractors"][order-of-extractors]
25///
26/// [order-of-extractors]: crate::extract#the-order-of-extractors
27///
28/// See [`JsonRejection`] for more details.
29///
30/// # Extractor example
31///
32/// ```rust,no_run
33/// use axum::{
34///     extract,
35///     routing::post,
36///     Router,
37/// };
38/// use serde::Deserialize;
39///
40/// #[derive(Deserialize)]
41/// struct CreateUser {
42///     email: String,
43///     password: String,
44/// }
45///
46/// async fn create_user(extract::Json(payload): extract::Json<CreateUser>) {
47///     // payload is a `CreateUser`
48/// }
49///
50/// let app = Router::new().route("/users", post(create_user));
51/// # let _: Router = app;
52/// ```
53///
54/// When used as a response, it can serialize any type that implements [`serde::Serialize`] to
55/// `JSON`, and will automatically set `Content-Type: application/json` header.
56///
57/// If the [`Serialize`] implementation decides to fail
58/// or if a map with non-string keys is used,
59/// a 500 response will be issued
60/// whose body is the error message in UTF-8.
61///
62/// # Response example
63///
64/// ```
65/// use axum::{
66///     extract::Path,
67///     routing::get,
68///     Router,
69///     Json,
70/// };
71/// use serde::Serialize;
72/// use uuid::Uuid;
73///
74/// #[derive(Serialize)]
75/// struct User {
76///     id: Uuid,
77///     username: String,
78/// }
79///
80/// async fn get_user(Path(user_id) : Path<Uuid>) -> Json<User> {
81///     let user = find_user(user_id).await;
82///     Json(user)
83/// }
84///
85/// async fn find_user(user_id: Uuid) -> User {
86///     // ...
87///     # unimplemented!()
88/// }
89///
90/// let app = Router::new().route("/users/{id}", get(get_user));
91/// # let _: Router = app;
92/// ```
93#[derive(Debug, Clone, Copy, Default)]
94#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
95#[must_use]
96pub struct Json<T>(pub T);
97
98impl<T, S> FromRequest<S> for Json<T>
99where
100    T: DeserializeOwned,
101    S: Send + Sync,
102{
103    type Rejection = JsonRejection;
104
105    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
106        if json_content_type(req.headers()) {
107            let bytes = Bytes::from_request(req, state).await?;
108            Self::from_bytes(&bytes)
109        } else {
110            Err(MissingJsonContentType.into())
111        }
112    }
113}
114
115fn json_content_type(headers: &HeaderMap) -> bool {
116    let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
117        content_type
118    } else {
119        return false;
120    };
121
122    let content_type = if let Ok(content_type) = content_type.to_str() {
123        content_type
124    } else {
125        return false;
126    };
127
128    let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
129        mime
130    } else {
131        return false;
132    };
133
134    let is_json_content_type = mime.type_() == "application"
135        && (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json"));
136
137    is_json_content_type
138}
139
140axum_core::__impl_deref!(Json);
141
142impl<T> From<T> for Json<T> {
143    fn from(inner: T) -> Self {
144        Self(inner)
145    }
146}
147
148impl<T> Json<T>
149where
150    T: DeserializeOwned,
151{
152    /// Construct a `Json<T>` from a byte slice. Most users should prefer to use the `FromRequest` impl
153    /// but special cases may require first extracting a `Request` into `Bytes` then optionally
154    /// constructing a `Json<T>`.
155    pub fn from_bytes(bytes: &[u8]) -> Result<Self, JsonRejection> {
156        let deserializer = &mut serde_json::Deserializer::from_slice(bytes);
157
158        let value = match serde_path_to_error::deserialize(deserializer) {
159            Ok(value) => value,
160            Err(err) => {
161                let rejection = match err.inner().classify() {
162                    serde_json::error::Category::Data => JsonDataError::from_err(err).into(),
163                    serde_json::error::Category::Syntax | serde_json::error::Category::Eof => {
164                        JsonSyntaxError::from_err(err).into()
165                    }
166                    serde_json::error::Category::Io => {
167                        if cfg!(debug_assertions) {
168                            // we don't use `serde_json::from_reader` and instead always buffer
169                            // bodies first, so we shouldn't encounter any IO errors
170                            unreachable!()
171                        } else {
172                            JsonSyntaxError::from_err(err).into()
173                        }
174                    }
175                };
176                return Err(rejection);
177            }
178        };
179
180        Ok(Json(value))
181    }
182}
183
184impl<T> IntoResponse for Json<T>
185where
186    T: Serialize,
187{
188    fn into_response(self) -> Response {
189        // Use a small initial capacity of 128 bytes like serde_json::to_vec
190        // https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189
191        let mut buf = BytesMut::with_capacity(128).writer();
192        match serde_json::to_writer(&mut buf, &self.0) {
193            Ok(()) => (
194                [(
195                    header::CONTENT_TYPE,
196                    HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
197                )],
198                buf.into_inner().freeze(),
199            )
200                .into_response(),
201            Err(err) => (
202                StatusCode::INTERNAL_SERVER_ERROR,
203                [(
204                    header::CONTENT_TYPE,
205                    HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
206                )],
207                err.to_string(),
208            )
209                .into_response(),
210        }
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::{routing::post, test_helpers::*, Router};
218    use serde::Deserialize;
219    use serde_json::{json, Value};
220
221    #[crate::test]
222    async fn deserialize_body() {
223        #[derive(Debug, Deserialize)]
224        struct Input {
225            foo: String,
226        }
227
228        let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
229
230        let client = TestClient::new(app);
231        let res = client.post("/").json(&json!({ "foo": "bar" })).await;
232        let body = res.text().await;
233
234        assert_eq!(body, "bar");
235    }
236
237    #[crate::test]
238    async fn consume_body_to_json_requires_json_content_type() {
239        #[derive(Debug, Deserialize)]
240        struct Input {
241            foo: String,
242        }
243
244        let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo }));
245
246        let client = TestClient::new(app);
247        let res = client.post("/").body(r#"{ "foo": "bar" }"#).await;
248
249        let status = res.status();
250
251        assert_eq!(status, StatusCode::UNSUPPORTED_MEDIA_TYPE);
252    }
253
254    #[crate::test]
255    async fn json_content_types() {
256        async fn valid_json_content_type(content_type: &str) -> bool {
257            println!("testing {content_type:?}");
258
259            let app = Router::new().route("/", post(|Json(_): Json<Value>| async {}));
260
261            let res = TestClient::new(app)
262                .post("/")
263                .header("content-type", content_type)
264                .body("{}")
265                .await;
266
267            res.status() == StatusCode::OK
268        }
269
270        assert!(valid_json_content_type("application/json").await);
271        assert!(valid_json_content_type("application/json; charset=utf-8").await);
272        assert!(valid_json_content_type("application/json;charset=utf-8").await);
273        assert!(valid_json_content_type("application/cloudevents+json").await);
274        assert!(!valid_json_content_type("text/json").await);
275    }
276
277    #[crate::test]
278    async fn invalid_json_syntax() {
279        let app = Router::new().route("/", post(|_: Json<serde_json::Value>| async {}));
280
281        let client = TestClient::new(app);
282        let res = client
283            .post("/")
284            .body("{")
285            .header("content-type", "application/json")
286            .await;
287
288        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
289    }
290
291    #[derive(Deserialize)]
292    struct Foo {
293        #[allow(dead_code)]
294        a: i32,
295        #[allow(dead_code)]
296        b: Vec<Bar>,
297    }
298
299    #[derive(Deserialize)]
300    struct Bar {
301        #[allow(dead_code)]
302        x: i32,
303        #[allow(dead_code)]
304        y: i32,
305    }
306
307    #[crate::test]
308    async fn invalid_json_data() {
309        let app = Router::new().route("/", post(|_: Json<Foo>| async {}));
310
311        let client = TestClient::new(app);
312        let res = client
313            .post("/")
314            .body("{\"a\": 1, \"b\": [{\"x\": 2}]}")
315            .header("content-type", "application/json")
316            .await;
317
318        assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);
319        let body_text = res.text().await;
320        assert_eq!(
321            body_text,
322            "Failed to deserialize the JSON body into the target type: b[0]: missing field `y` at line 1 column 23"
323        );
324    }
325}