axum/
json.rs

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