Skip to main content

actix_web/types/
json.rs

1//! For JSON helper documentation, see [`Json`].
2
3use std::{
4    fmt,
5    future::Future,
6    marker::PhantomData,
7    ops,
8    pin::Pin,
9    sync::Arc,
10    task::{Context, Poll},
11};
12
13use actix_http::{error::PayloadError, Payload};
14use bytes::BytesMut;
15use futures_core::{ready, Stream as _};
16use serde::{de::DeserializeOwned, Serialize};
17
18#[cfg(feature = "__compress")]
19use crate::dev::Decompress;
20use crate::{
21    body::EitherBody,
22    error::{Error, JsonPayloadError},
23    extract::FromRequest,
24    http::header::{ContentLength, Header as _},
25    request::HttpRequest,
26    web, HttpMessage, HttpResponse, Responder,
27};
28
29/// JSON extractor and responder.
30///
31/// `Json` has two uses: JSON responses, and extracting typed data from JSON request payloads.
32///
33/// # Extractor
34/// To extract typed data from a request body, the inner type `T` must implement the
35/// [`serde::Deserialize`] trait.
36///
37/// Use [`JsonConfig`] to configure extraction options.
38///
39/// ```
40/// use actix_web::{post, web, App};
41/// use serde::Deserialize;
42///
43/// #[derive(Deserialize)]
44/// struct Info {
45///     username: String,
46/// }
47///
48/// /// deserialize `Info` from request's body
49/// #[post("/")]
50/// async fn index(info: web::Json<Info>) -> String {
51///     format!("Welcome {}!", info.username)
52/// }
53/// ```
54///
55/// # Responder
56/// The `Json` type  JSON formatted responses. A handler may return a value of type
57/// `Json<T>` where `T` is the type of a structure to serialize into JSON. The type `T` must
58/// implement [`serde::Serialize`].
59///
60/// ```
61/// use actix_web::{post, web, HttpRequest};
62/// use serde::Serialize;
63///
64/// #[derive(Serialize)]
65/// struct Info {
66///     name: String,
67/// }
68///
69/// #[post("/{name}")]
70/// async fn index(req: HttpRequest) -> web::Json<Info> {
71///     web::Json(Info {
72///         name: req.match_info().get("name").unwrap().to_owned(),
73///     })
74/// }
75/// ```
76#[derive(Debug)]
77pub struct Json<T>(pub T);
78
79impl<T> Json<T> {
80    /// Unwrap into inner `T` value.
81    pub fn into_inner(self) -> T {
82        self.0
83    }
84}
85
86impl<T> ops::Deref for Json<T> {
87    type Target = T;
88
89    fn deref(&self) -> &T {
90        &self.0
91    }
92}
93
94impl<T> ops::DerefMut for Json<T> {
95    fn deref_mut(&mut self) -> &mut T {
96        &mut self.0
97    }
98}
99
100impl<T: fmt::Display> fmt::Display for Json<T> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        fmt::Display::fmt(&self.0, f)
103    }
104}
105
106impl<T: Serialize> Serialize for Json<T> {
107    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108    where
109        S: serde::Serializer,
110    {
111        self.0.serialize(serializer)
112    }
113}
114
115/// Creates response with OK status code, correct content type header, and serialized JSON payload.
116///
117/// If serialization failed
118impl<T: Serialize> Responder for Json<T> {
119    type Body = EitherBody<String>;
120
121    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
122        match serde_json::to_string(&self.0) {
123            Ok(body) => match HttpResponse::Ok()
124                .content_type(mime::APPLICATION_JSON)
125                .message_body(body)
126            {
127                Ok(res) => res.map_into_left_body(),
128                Err(err) => HttpResponse::from_error(err).map_into_right_body(),
129            },
130
131            Err(err) => {
132                HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body()
133            }
134        }
135    }
136}
137
138/// See [here](#extractor) for example of usage as an extractor.
139impl<T: DeserializeOwned> FromRequest for Json<T> {
140    type Error = Error;
141    type Future = JsonExtractFut<T>;
142
143    #[inline]
144    fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
145        let config = JsonConfig::from_req(req);
146
147        let limit = config.limit;
148        let ctype_required = config.content_type_required;
149        let ctype_fn = config.content_type.as_deref();
150        let err_handler = config.err_handler.clone();
151
152        JsonExtractFut {
153            req: Some(req.clone()),
154            fut: JsonBody::new(req, payload, ctype_fn, ctype_required).limit(limit),
155            err_handler,
156        }
157    }
158}
159
160type JsonErrorHandler = Option<Arc<dyn Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync>>;
161
162pub struct JsonExtractFut<T> {
163    req: Option<HttpRequest>,
164    fut: JsonBody<T>,
165    err_handler: JsonErrorHandler,
166}
167
168impl<T: DeserializeOwned> Future for JsonExtractFut<T> {
169    type Output = Result<Json<T>, Error>;
170
171    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
172        let this = self.get_mut();
173
174        let res = ready!(Pin::new(&mut this.fut).poll(cx));
175
176        let res = match res {
177            Err(err) => {
178                let req = this.req.take().unwrap();
179                log::debug!(
180                    "Failed to deserialize Json from payload. \
181                         Request path: {}",
182                    req.path()
183                );
184
185                if let Some(err_handler) = this.err_handler.as_ref() {
186                    Err((*err_handler)(err, &req))
187                } else {
188                    Err(err.into())
189                }
190            }
191            Ok(data) => Ok(Json(data)),
192        };
193
194        Poll::Ready(res)
195    }
196}
197
198/// `Json` extractor configuration.
199///
200/// # Examples
201/// ```
202/// use actix_web::{error, post, web, App, FromRequest, HttpResponse};
203/// use serde::Deserialize;
204///
205/// #[derive(Deserialize)]
206/// struct Info {
207///     name: String,
208/// }
209///
210/// // `Json` extraction is bound by custom `JsonConfig` applied to App.
211/// #[post("/")]
212/// async fn index(info: web::Json<Info>) -> String {
213///     format!("Welcome {}!", info.name)
214/// }
215///
216/// // custom `Json` extractor configuration
217/// let json_cfg = web::JsonConfig::default()
218///     // limit request payload size
219///     .limit(4096)
220///     // only accept text/plain content type
221///     .content_type(|mime| mime == mime::TEXT_PLAIN)
222///     // use custom error handler
223///     .error_handler(|err, req| {
224///         error::InternalError::from_response(err, HttpResponse::Conflict().into()).into()
225///     });
226///
227/// App::new()
228///     .app_data(json_cfg)
229///     .service(index);
230/// ```
231#[derive(Clone)]
232pub struct JsonConfig {
233    limit: usize,
234    err_handler: JsonErrorHandler,
235    content_type: Option<Arc<dyn Fn(mime::Mime) -> bool + Send + Sync>>,
236    content_type_required: bool,
237}
238
239impl JsonConfig {
240    /// Set maximum accepted payload size. By default this limit is 2MB.
241    pub fn limit(mut self, limit: usize) -> Self {
242        self.limit = limit;
243        self
244    }
245
246    /// Set custom error handler.
247    pub fn error_handler<F>(mut self, f: F) -> Self
248    where
249        F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
250    {
251        self.err_handler = Some(Arc::new(f));
252        self
253    }
254
255    /// Set predicate for allowed content types.
256    pub fn content_type<F>(mut self, predicate: F) -> Self
257    where
258        F: Fn(mime::Mime) -> bool + Send + Sync + 'static,
259    {
260        self.content_type = Some(Arc::new(predicate));
261        self
262    }
263
264    /// Sets whether or not the request must have a `Content-Type` header to be parsed.
265    pub fn content_type_required(mut self, content_type_required: bool) -> Self {
266        self.content_type_required = content_type_required;
267        self
268    }
269
270    /// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
271    /// back to the default payload config.
272    fn from_req(req: &HttpRequest) -> &Self {
273        req.app_data::<Self>()
274            .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
275            .unwrap_or(&DEFAULT_CONFIG)
276    }
277}
278
279const DEFAULT_LIMIT: usize = 2_097_152; // 2 mb
280
281/// Allow shared refs used as default.
282const DEFAULT_CONFIG: JsonConfig = JsonConfig {
283    limit: DEFAULT_LIMIT,
284    err_handler: None,
285    content_type: None,
286    content_type_required: true,
287};
288
289impl Default for JsonConfig {
290    fn default() -> Self {
291        DEFAULT_CONFIG
292    }
293}
294
295/// Future that resolves to some `T` when parsed from a JSON payload.
296///
297/// Can deserialize any type `T` that implements [`Deserialize`][serde::Deserialize].
298///
299/// Returns error if:
300/// - `Content-Type` is not `application/json` when `ctype_required` (passed to [`new`][Self::new])
301///   is `true`.
302/// - `Content-Length` is greater than [limit](JsonBody::limit()).
303/// - The payload, when consumed, is not valid JSON.
304pub enum JsonBody<T> {
305    Error(Option<JsonPayloadError>),
306    Body {
307        limit: usize,
308        /// Length as reported by `Content-Length` header, if present.
309        length: Option<usize>,
310        #[cfg(feature = "__compress")]
311        payload: Decompress<Payload>,
312        #[cfg(not(feature = "__compress"))]
313        payload: Payload,
314        buf: BytesMut,
315        _res: PhantomData<T>,
316    },
317}
318
319impl<T> Unpin for JsonBody<T> {}
320
321impl<T: DeserializeOwned> JsonBody<T> {
322    /// Create a new future to decode a JSON request payload.
323    #[allow(clippy::borrow_interior_mutable_const)]
324    pub fn new(
325        req: &HttpRequest,
326        payload: &mut Payload,
327        ctype_fn: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>,
328        ctype_required: bool,
329    ) -> Self {
330        // check content-type
331        let can_parse_json = match (ctype_required, req.mime_type()) {
332            (true, Ok(Some(mime))) => {
333                mime.subtype() == mime::JSON
334                    || mime.suffix() == Some(mime::JSON)
335                    || ctype_fn.is_some_and(|predicate| predicate(mime))
336            }
337
338            // if content-type is expected but not parsable as mime type, bail
339            (true, _) => false,
340
341            // if content-type validation is disabled, assume payload is JSON
342            // even when content-type header is missing or invalid mime type
343            (false, _) => true,
344        };
345
346        if !can_parse_json {
347            return JsonBody::Error(Some(JsonPayloadError::ContentType));
348        }
349
350        let length = ContentLength::parse(req).ok().map(|x| x.0);
351
352        // Notice the content-length is not checked against limit of json config here.
353        // As the internal usage always call JsonBody::limit after JsonBody::new.
354        // And limit check to return an error variant of JsonBody happens there.
355
356        let payload = {
357            cfg_if::cfg_if! {
358                if #[cfg(feature = "__compress")] {
359                    Decompress::from_headers(payload.take(), req.headers())
360                        .limit(DEFAULT_LIMIT)
361                } else {
362                    payload.take()
363                }
364            }
365        };
366
367        JsonBody::Body {
368            limit: DEFAULT_LIMIT,
369            length,
370            payload,
371            buf: BytesMut::with_capacity(8192),
372            _res: PhantomData,
373        }
374    }
375
376    /// Set maximum accepted payload size. The default limit is 2MB.
377    pub fn limit(self, limit: usize) -> Self {
378        match self {
379            JsonBody::Body {
380                length,
381                payload,
382                buf,
383                ..
384            } => {
385                if let Some(len) = length {
386                    if len > limit {
387                        return JsonBody::Error(Some(JsonPayloadError::OverflowKnownLength {
388                            length: len,
389                            limit,
390                        }));
391                    }
392                }
393
394                #[cfg(feature = "__compress")]
395                let payload = payload.limit(limit);
396
397                JsonBody::Body {
398                    limit,
399                    length,
400                    payload,
401                    buf,
402                    _res: PhantomData,
403                }
404            }
405            JsonBody::Error(err) => JsonBody::Error(err),
406        }
407    }
408}
409
410impl<T: DeserializeOwned> Future for JsonBody<T> {
411    type Output = Result<T, JsonPayloadError>;
412
413    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
414        let this = self.get_mut();
415
416        match this {
417            JsonBody::Body {
418                limit,
419                buf,
420                payload,
421                ..
422            } => loop {
423                let res = ready!(Pin::new(&mut *payload).poll_next(cx));
424                match res {
425                    Some(chunk) => {
426                        let chunk = chunk.map_err(|err| match err {
427                            PayloadError::Overflow => JsonPayloadError::Overflow { limit: *limit },
428                            err => JsonPayloadError::Payload(err),
429                        })?;
430                        let buf_len = buf.len() + chunk.len();
431                        if buf_len > *limit {
432                            return Poll::Ready(Err(JsonPayloadError::Overflow { limit: *limit }));
433                        } else {
434                            buf.extend_from_slice(&chunk);
435                        }
436                    }
437                    None => {
438                        let json = serde_json::from_slice::<T>(buf)
439                            .map_err(JsonPayloadError::Deserialize)?;
440                        return Poll::Ready(Ok(json));
441                    }
442                }
443            },
444            JsonBody::Error(err) => Poll::Ready(Err(err.take().unwrap())),
445        }
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use bytes::Bytes;
452    use serde::{Deserialize, Serialize};
453
454    use super::*;
455    use crate::{
456        body,
457        error::InternalError,
458        http::{
459            header::{self, CONTENT_LENGTH, CONTENT_TYPE},
460            StatusCode,
461        },
462        test::{assert_body_eq, TestRequest},
463    };
464
465    #[derive(Serialize, Deserialize, PartialEq, Debug)]
466    struct MyObject {
467        name: String,
468    }
469
470    fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
471        match err {
472            JsonPayloadError::Overflow { .. } => {
473                matches!(other, JsonPayloadError::Overflow { .. })
474            }
475            JsonPayloadError::OverflowKnownLength { .. } => {
476                matches!(other, JsonPayloadError::OverflowKnownLength { .. })
477            }
478            JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType),
479            _ => false,
480        }
481    }
482
483    #[actix_rt::test]
484    async fn test_responder() {
485        let req = TestRequest::default().to_http_request();
486
487        let j = Json(MyObject {
488            name: "test".to_string(),
489        });
490        let res = j.respond_to(&req);
491        assert_eq!(res.status(), StatusCode::OK);
492        assert_eq!(
493            res.headers().get(header::CONTENT_TYPE).unwrap(),
494            header::HeaderValue::from_static("application/json")
495        );
496        assert_body_eq!(res, b"{\"name\":\"test\"}");
497    }
498
499    #[actix_rt::test]
500    async fn test_custom_error_responder() {
501        let (req, mut pl) = TestRequest::default()
502            .insert_header((
503                header::CONTENT_TYPE,
504                header::HeaderValue::from_static("application/json"),
505            ))
506            .insert_header((
507                header::CONTENT_LENGTH,
508                header::HeaderValue::from_static("16"),
509            ))
510            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
511            .app_data(JsonConfig::default().limit(10).error_handler(|err, _| {
512                let msg = MyObject {
513                    name: "invalid request".to_string(),
514                };
515                let resp = HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap());
516                InternalError::from_response(err, resp).into()
517            }))
518            .to_http_parts();
519
520        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
521        let resp = HttpResponse::from_error(s.unwrap_err());
522        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
523
524        let body = body::to_bytes(resp.into_body()).await.unwrap();
525        let msg: MyObject = serde_json::from_slice(&body).unwrap();
526        assert_eq!(msg.name, "invalid request");
527    }
528
529    #[actix_rt::test]
530    async fn test_extract() {
531        let (req, mut pl) = TestRequest::default()
532            .insert_header((
533                header::CONTENT_TYPE,
534                header::HeaderValue::from_static("application/json"),
535            ))
536            .insert_header((
537                header::CONTENT_LENGTH,
538                header::HeaderValue::from_static("16"),
539            ))
540            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
541            .to_http_parts();
542
543        let s = Json::<MyObject>::from_request(&req, &mut pl).await.unwrap();
544        assert_eq!(s.name, "test");
545        assert_eq!(
546            s.into_inner(),
547            MyObject {
548                name: "test".to_string()
549            }
550        );
551
552        let (req, mut pl) = TestRequest::default()
553            .insert_header((
554                header::CONTENT_TYPE,
555                header::HeaderValue::from_static("application/json"),
556            ))
557            .insert_header((
558                header::CONTENT_LENGTH,
559                header::HeaderValue::from_static("16"),
560            ))
561            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
562            .app_data(JsonConfig::default().limit(10))
563            .to_http_parts();
564
565        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
566        assert!(format!("{}", s.err().unwrap())
567            .contains("JSON payload (16 bytes) is larger than allowed (limit: 10 bytes)."));
568
569        let (req, mut pl) = TestRequest::default()
570            .insert_header((
571                header::CONTENT_TYPE,
572                header::HeaderValue::from_static("application/json"),
573            ))
574            .insert_header((
575                header::CONTENT_LENGTH,
576                header::HeaderValue::from_static("16"),
577            ))
578            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
579            .app_data(
580                JsonConfig::default()
581                    .limit(10)
582                    .error_handler(|_, _| JsonPayloadError::ContentType.into()),
583            )
584            .to_http_parts();
585        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
586        assert!(format!("{}", s.err().unwrap()).contains("Content type error"));
587    }
588
589    #[actix_rt::test]
590    async fn test_json_body() {
591        let (req, mut pl) = TestRequest::default().to_http_parts();
592        let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
593        assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
594
595        let (req, mut pl) = TestRequest::default()
596            .insert_header((
597                header::CONTENT_TYPE,
598                header::HeaderValue::from_static("application/text"),
599            ))
600            .to_http_parts();
601        let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
602        assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
603
604        let (req, mut pl) = TestRequest::default()
605            .insert_header((
606                header::CONTENT_TYPE,
607                header::HeaderValue::from_static("application/json"),
608            ))
609            .insert_header((
610                header::CONTENT_LENGTH,
611                header::HeaderValue::from_static("10000"),
612            ))
613            .to_http_parts();
614
615        let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
616            .limit(100)
617            .await;
618        assert!(json_eq(
619            json.err().unwrap(),
620            JsonPayloadError::OverflowKnownLength {
621                length: 10000,
622                limit: 100
623            }
624        ));
625
626        let (mut req, mut pl) = TestRequest::default()
627            .insert_header((
628                header::CONTENT_TYPE,
629                header::HeaderValue::from_static("application/json"),
630            ))
631            .set_payload(Bytes::from_static(&[0u8; 1000]))
632            .to_http_parts();
633
634        req.head_mut().headers_mut().remove(header::CONTENT_LENGTH);
635        let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true)
636            .limit(100)
637            .await;
638
639        assert!(json_eq(
640            json.err().unwrap(),
641            JsonPayloadError::Overflow { limit: 100 }
642        ));
643
644        let (req, mut pl) = TestRequest::default()
645            .insert_header((
646                header::CONTENT_TYPE,
647                header::HeaderValue::from_static("application/json"),
648            ))
649            .insert_header((
650                header::CONTENT_LENGTH,
651                header::HeaderValue::from_static("16"),
652            ))
653            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
654            .to_http_parts();
655
656        let json = JsonBody::<MyObject>::new(&req, &mut pl, None, true).await;
657        assert_eq!(
658            json.ok().unwrap(),
659            MyObject {
660                name: "test".to_owned()
661            }
662        );
663    }
664
665    #[actix_rt::test]
666    async fn test_with_json_and_bad_content_type() {
667        let (req, mut pl) = TestRequest::default()
668            .insert_header((
669                header::CONTENT_TYPE,
670                header::HeaderValue::from_static("text/plain"),
671            ))
672            .insert_header((
673                header::CONTENT_LENGTH,
674                header::HeaderValue::from_static("16"),
675            ))
676            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
677            .app_data(JsonConfig::default().limit(4096))
678            .to_http_parts();
679
680        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
681        assert!(s.is_err())
682    }
683
684    #[actix_rt::test]
685    async fn test_with_json_and_good_custom_content_type() {
686        let (req, mut pl) = TestRequest::default()
687            .insert_header((
688                header::CONTENT_TYPE,
689                header::HeaderValue::from_static("text/plain"),
690            ))
691            .insert_header((
692                header::CONTENT_LENGTH,
693                header::HeaderValue::from_static("16"),
694            ))
695            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
696            .app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
697                mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
698            }))
699            .to_http_parts();
700
701        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
702        assert!(s.is_ok())
703    }
704
705    #[actix_rt::test]
706    async fn test_with_json_and_bad_custom_content_type() {
707        let (req, mut pl) = TestRequest::default()
708            .insert_header((
709                header::CONTENT_TYPE,
710                header::HeaderValue::from_static("text/html"),
711            ))
712            .insert_header((
713                header::CONTENT_LENGTH,
714                header::HeaderValue::from_static("16"),
715            ))
716            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
717            .app_data(JsonConfig::default().content_type(|mime: mime::Mime| {
718                mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN
719            }))
720            .to_http_parts();
721
722        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
723        assert!(s.is_err())
724    }
725
726    #[actix_rt::test]
727    async fn test_json_with_no_content_type() {
728        let (req, mut pl) = TestRequest::default()
729            .insert_header((
730                header::CONTENT_LENGTH,
731                header::HeaderValue::from_static("16"),
732            ))
733            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
734            .app_data(JsonConfig::default().content_type_required(false))
735            .to_http_parts();
736
737        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
738        assert!(s.is_ok())
739    }
740
741    #[actix_rt::test]
742    async fn test_json_ignoring_content_type() {
743        let (req, mut pl) = TestRequest::default()
744            .insert_header((
745                header::CONTENT_LENGTH,
746                header::HeaderValue::from_static("16"),
747            ))
748            .insert_header((
749                header::CONTENT_TYPE,
750                header::HeaderValue::from_static("invalid/value"),
751            ))
752            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
753            .app_data(JsonConfig::default().content_type_required(false))
754            .to_http_parts();
755
756        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
757        assert!(s.is_ok());
758    }
759
760    #[actix_rt::test]
761    async fn test_with_config_in_data_wrapper() {
762        let (req, mut pl) = TestRequest::default()
763            .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON))
764            .insert_header((CONTENT_LENGTH, 16))
765            .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
766            .app_data(web::Data::new(JsonConfig::default().limit(10)))
767            .to_http_parts();
768
769        let s = Json::<MyObject>::from_request(&req, &mut pl).await;
770        assert!(s.is_err());
771
772        let err_str = s.err().unwrap().to_string();
773        assert!(
774            err_str.contains("JSON payload (16 bytes) is larger than allowed (limit: 10 bytes).")
775        );
776    }
777}