Repository: swaggest/rest
Pull request #164 (2023-07-23 17:22:29)

Add support for OpenAPI deep object decoding

github-actions[bot]: ### Lines Of Code

| Language  | Files |   Lines    |    Code    | Comments  |   Blanks   | Complexity |     Bytes      |
|-----------|-------|------------|------------|-----------|------------|------------|----------------|
| Go        | 106   | 7759 (+72) | 5635 (+44) | 521 (+17) | 1603 (+11) | 957 (+9)   | 188.1K (+1.7K) |
| Go (test) | 48    | 5580 (+71) | 4310 (+64) | 187       | 1083 (+7)  | 180        | 155.2K (+2K)   |
| JSON      | 3     | 1581 (+14) | 1581 (+14) | 0         | 0          | 0          | 65.7K (+784B)  |
<!-- Sticky Pull Request CommentLOC -->
github-actions[bot]: ### Go API Changes

<pre>
# summary
Inferred base version: v0.2.52
Suggested version: v0.3.0
</pre>
<!-- Sticky Pull Request Commentgorelease -->
github-actions[bot]: ### Benchmark Result
<details><summary>Benchmark diff with base branch</summary>

```
name                                    old time/op    new time/op    delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2    1.35µs ± 1%    1.35µs ± 1%    ~     (p=0.889 n=5+5)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                           705ns ± 0%     712ns ± 1%  +1.00%  (p=0.008 n=5+5)
DecoderFunc_Decode-2                      2.02µs ± 2%    2.02µs ± 1%    ~     (p=0.937 n=6+6)
Decoder_Decode_json-2                     21.8µs ± 0%    22.0µs ± 1%  +0.64%  (p=0.015 n=6+6)
Decoder_Decode_queryObject-2              5.01µs ± 0%    4.90µs ± 1%  -2.23%  (p=0.002 n=6+6)
Decoder_Decode_jsonParam-2                1.81µs ± 1%    1.83µs ± 1%  +0.87%  (p=0.002 n=6+6)
DecoderFactory_SetDecoderFunc-2           1.71µs ± 1%    1.71µs ± 0%    ~     (p=0.165 n=6+5)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              11.0µs ± 1%    11.0µs ± 1%    ~     (p=0.329 n=5+6)
Middleware_control-2                      3.50µs ± 4%    3.47µs ± 7%    ~     (p=0.699 n=6+6)

name                                    old alloc/op   new alloc/op   delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2    2.46kB ± 0%    2.46kB ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                            440B ± 0%      440B ± 0%    ~     (all equal)
DecoderFunc_Decode-2                      1.51kB ± 0%    1.51kB ± 0%    ~     (all equal)
Decoder_Decode_json-2                     12.2kB ± 0%    12.2kB ± 0%    ~     (all equal)
Decoder_Decode_queryObject-2              2.00kB ± 0%    2.00kB ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-2                  736B ± 0%      736B ± 0%    ~     (all equal)
DecoderFactory_SetDecoderFunc-2           1.02kB ± 0%    1.02kB ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              1.15kB ± 2%    1.17kB ± 2%    ~     (p=0.087 n=6+6)
Middleware_control-2                      11.2kB ± 0%    11.2kB ± 0%    ~     (all equal)

name                                    old allocs/op  new allocs/op  delta
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2      8.00 ± 0%      8.00 ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                            4.00 ± 0%      4.00 ± 0%    ~     (all equal)
DecoderFunc_Decode-2                        12.0 ± 0%      12.0 ± 0%    ~     (all equal)
Decoder_Decode_json-2                        177 ± 0%       177 ± 0%    ~     (all equal)
Decoder_Decode_queryObject-2                36.0 ± 0%      36.0 ± 0%    ~     (all equal)
Decoder_Decode_jsonParam-2                  13.0 ± 0%      13.0 ± 0%    ~     (all equal)
DecoderFactory_SetDecoderFunc-2             16.0 ± 0%      16.0 ± 0%    ~     (all equal)
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                                11.0 ± 0%      11.0 ± 0%    ~     (all equal)
Middleware_control-2                        9.00 ± 0%      9.00 ± 0%    ~     (all equal)
```
</details>

<details><summary>Benchmark result</summary>

```
name                                    time/op
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2  1.35µs ± 1%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                         712ns ± 1%
DecoderFunc_Decode-2                    2.02µs ± 1%
Decoder_Decode_json-2                   22.0µs ± 1%
Decoder_Decode_queryObject-2            4.90µs ± 1%
Decoder_Decode_jsonParam-2              1.83µs ± 1%
DecoderFactory_SetDecoderFunc-2         1.71µs ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                            11.0µs ± 1%
Middleware_control-2                    3.47µs ± 7%

name                                    alloc/op
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2  2.46kB ± 0%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                          440B ± 0%
DecoderFunc_Decode-2                    1.51kB ± 0%
Decoder_Decode_json-2                   12.2kB ± 0%
Decoder_Decode_queryObject-2            2.00kB ± 0%
Decoder_Decode_jsonParam-2                736B ± 0%
DecoderFactory_SetDecoderFunc-2         1.02kB ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                            1.17kB ± 2%
Middleware_control-2                    11.2kB ± 0%

name                                    allocs/op
pkg:github.com/swaggest/rest/jsonschema goos:linux goarch:amd64
RequestValidator_ValidateRequestData-2    8.00 ± 0%
pkg:github.com/swaggest/rest/request goos:linux goarch:amd64
Decoder_Decode-2                          4.00 ± 0%
DecoderFunc_Decode-2                      12.0 ± 0%
Decoder_Decode_json-2                      177 ± 0%
Decoder_Decode_queryObject-2              36.0 ± 0%
Decoder_Decode_jsonParam-2                13.0 ± 0%
DecoderFactory_SetDecoderFunc-2           16.0 ± 0%
pkg:github.com/swaggest/rest/response/gzip goos:linux goarch:amd64
Middleware-2                              11.0 ± 0%
Middleware_control-2                      9.00 ± 0%
```
</details>
<!-- Sticky Pull Request Commentbench -->
github-actions[bot]: ### Examples Benchmark Result
<details><summary>Benchmark diff with base branch</summary>

```
name                       old time/op    new time/op    delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                25.3µs ± 5%    23.8µs ± 1%   -5.71%  (p=0.008 n=5+5)
_directGzipHead-2            25.3µs ± 1%    24.9µs ±13%     ~     (p=0.151 n=5+5)
_noDirectGzip-2               135µs ± 1%     134µs ± 2%     ~     (p=0.310 n=5+5)
_directGzip_decode-2          494µs ± 4%     488µs ± 5%     ~     (p=0.421 n=5+5)
_noDirectGzip_decode-2        136µs ± 1%     135µs ± 1%     ~     (p=0.095 n=5+5)
_jsonBody-2                  49.0µs ± 0%    49.3µs ± 3%     ~     (p=0.730 n=4+5)
_jsonBodyValidation-2        58.8µs ± 3%    58.2µs ± 2%     ~     (p=0.421 n=5+5)
_outputHeaders-2             25.5µs ± 2%    25.1µs ± 3%     ~     (p=0.222 n=5+5)
_requestResponseMapping-2    50.8µs ± 3%    50.5µs ± 1%     ~     (p=0.690 n=5+5)
_validation-2                53.7µs ± 4%    53.6µs ± 1%     ~     (p=0.841 n=5+5)
_noValidation-2              37.3µs ± 2%    37.8µs ± 2%     ~     (p=0.222 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                24.9µs ± 3%    26.3µs ±12%     ~     (p=0.056 n=5+5)
_directGzipHead-2            25.1µs ± 3%    25.3µs ± 1%     ~     (p=0.841 n=5+5)
_noDirectGzip-2               128µs ± 1%     129µs ± 1%     ~     (p=0.095 n=5+5)
_directGzip_decode-2          473µs ± 2%     485µs ± 2%   +2.51%  (p=0.032 n=5+5)
_noDirectGzip_decode-2        130µs ± 1%     132µs ± 6%     ~     (p=0.690 n=5+5)
_htmlResponse-2              42.1µs ± 4%    43.6µs ± 1%   +3.63%  (p=0.032 n=5+5)
_jsonBodyManual-2            30.2µs ± 1%    30.5µs ± 1%     ~     (p=0.222 n=5+5)
_jsonBody-2                  44.7µs ± 2%    45.7µs ± 3%     ~     (p=0.310 n=5+5)
_jsonBodyValidation-2        58.8µs ± 2%    60.7µs ± 3%   +3.24%  (p=0.016 n=5+5)
_outputHeaders-2             43.0µs ± 3%    43.3µs ± 3%     ~     (p=1.000 n=5+5)
_requestResponseMapping-2    50.6µs ± 4%    51.8µs ± 2%     ~     (p=0.151 n=5+5)
_validation-2                54.3µs ± 3%    55.8µs ± 4%     ~     (p=0.095 n=5+5)
_noValidation-2              38.7µs ± 2%    40.1µs ± 2%   +3.70%  (p=0.008 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2               26.7µs ± 1%    28.6µs ±13%   +7.18%  (p=0.008 n=5+5)
_ok-2                        27.4µs ± 3%    28.4µs ± 2%   +3.79%  (p=0.032 n=5+5)
_invalidBody-2               40.1µs ± 2%    42.5µs ± 1%   +5.93%  (p=0.008 n=5+5)

name                       old 50%:ms     new 50%:ms     delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  1.22 ± 8%      1.15 ± 5%     ~     (p=0.175 n=5+5)
_directGzipHead-2              1.17 ± 7%      1.15 ± 7%     ~     (p=0.690 n=5+5)
_noDirectGzip-2                6.64 ± 3%      6.36 ± 6%     ~     (p=0.056 n=5+5)
_directGzip_decode-2           18.9 ± 7%      18.4 ±10%     ~     (p=0.421 n=5+5)
_noDirectGzip_decode-2         6.73 ± 4%      6.44 ± 3%   -4.36%  (p=0.032 n=5+5)
_jsonBody-2                    2.20 ± 5%      2.24 ± 6%     ~     (p=0.421 n=5+5)
_jsonBodyValidation-2          2.63 ± 8%      2.70 ± 4%     ~     (p=0.548 n=5+5)
_outputHeaders-2               1.13 ± 3%      1.16 ± 2%   +2.87%  (p=0.016 n=5+5)
_requestResponseMapping-2      2.29 ± 3%      2.24 ± 5%     ~     (p=0.421 n=5+5)
_validation-2                  2.50 ± 6%      2.46 ± 7%     ~     (p=0.690 n=5+5)
_noValidation-2                1.64 ± 6%      1.66 ± 3%     ~     (p=0.421 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  1.13 ± 1%      1.20 ± 4%   +5.75%  (p=0.016 n=4+5)
_directGzipHead-2              1.18 ± 7%      1.20 ± 6%     ~     (p=0.651 n=5+5)
_noDirectGzip-2                6.31 ± 2%      6.15 ± 6%     ~     (p=0.151 n=5+5)
_directGzip_decode-2           18.0 ± 5%      18.9 ± 9%     ~     (p=0.151 n=5+5)
_noDirectGzip_decode-2         6.22 ± 7%      6.29 ± 4%     ~     (p=0.841 n=5+5)
_htmlResponse-2                1.90 ± 4%      1.93 ± 6%     ~     (p=0.310 n=5+5)
_jsonBodyManual-2              1.42 ± 6%      1.42 ± 5%     ~     (p=0.841 n=5+5)
_jsonBody-2                    2.04 ± 1%      2.00 ± 3%     ~     (p=0.095 n=4+5)
_jsonBodyValidation-2          2.77 ± 8%      2.68 ± 6%     ~     (p=0.444 n=5+5)
_outputHeaders-2               1.82 ± 5%      1.85 ± 5%     ~     (p=0.421 n=5+5)
_requestResponseMapping-2      2.18 ± 8%      2.25 ± 3%     ~     (p=0.310 n=5+5)
_validation-2                  2.28 ± 3%      2.50 ±10%   +9.88%  (p=0.016 n=5+5)
_noValidation-2                1.72 ± 4%      1.72 ± 4%     ~     (p=1.000 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 1.27 ± 5%      1.31 ± 8%     ~     (p=0.198 n=5+5)
_ok-2                          1.33 ± 7%      1.35 ± 5%     ~     (p=0.738 n=5+5)
_invalidBody-2                 1.74 ± 6%      1.87 ± 6%     ~     (p=0.056 n=5+5)

name                       old 90%:ms     new 90%:ms     delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  2.17 ± 8%      2.00 ± 4%   -7.97%  (p=0.008 n=5+5)
_directGzipHead-2              2.17 ± 7%      2.07 ± 5%     ~     (p=0.222 n=5+5)
_noDirectGzip-2                11.7 ± 4%      11.6 ± 4%     ~     (p=0.421 n=5+5)
_directGzip_decode-2           40.5 ± 7%      41.6 ± 3%     ~     (p=0.548 n=5+5)
_noDirectGzip_decode-2         11.4 ± 3%      11.5 ± 3%     ~     (p=0.952 n=5+5)
_jsonBody-2                    4.53 ± 7%      4.50 ± 7%     ~     (p=1.000 n=5+5)
_jsonBodyValidation-2          5.09 ± 1%      5.11 ± 4%     ~     (p=0.690 n=5+5)
_outputHeaders-2               2.13 ±11%      2.11 ± 5%     ~     (p=1.000 n=5+5)
_requestResponseMapping-2      4.54 ± 6%      4.41 ± 5%     ~     (p=0.310 n=5+5)
_validation-2                  4.89 ± 7%      4.71 ± 5%     ~     (p=0.222 n=5+5)
_noValidation-2                3.44 ± 3%      3.54 ± 6%     ~     (p=0.222 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  2.09 ± 7%      2.27 ±11%     ~     (p=0.056 n=5+5)
_directGzipHead-2              2.08 ± 5%      2.20 ± 5%     ~     (p=0.056 n=5+5)
_noDirectGzip-2                11.0 ± 2%      11.3 ± 3%     ~     (p=0.103 n=5+5)
_directGzip_decode-2           39.7 ± 6%      39.6 ± 2%     ~     (p=0.690 n=5+5)
_noDirectGzip_decode-2         11.4 ± 5%      10.9 ± 2%   -4.52%  (p=0.016 n=5+4)
_htmlResponse-2                3.88 ± 7%      3.98 ± 6%     ~     (p=0.421 n=5+5)
_jsonBodyManual-2              2.56 ± 4%      2.66 ± 4%     ~     (p=0.095 n=5+5)
_jsonBody-2                    4.24 ± 7%      4.41 ± 8%     ~     (p=0.310 n=5+5)
_jsonBodyValidation-2          5.10 ± 7%      5.40 ± 6%     ~     (p=0.056 n=5+5)
_outputHeaders-2               4.08 ± 4%      4.15 ± 9%     ~     (p=1.000 n=5+5)
_requestResponseMapping-2      4.57 ± 4%      4.71 ± 2%     ~     (p=0.222 n=5+5)
_validation-2                  4.95 ± 6%      5.06 ± 8%     ~     (p=0.690 n=5+5)
_noValidation-2                3.70 ± 2%      3.94 ± 4%   +6.41%  (p=0.008 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 2.33 ± 5%      2.39 ± 5%     ~     (p=0.548 n=5+5)
_ok-2                          2.34 ± 9%      2.46 ± 4%     ~     (p=0.222 n=5+5)
_invalidBody-2                 3.86 ± 5%      4.36 ± 5%  +13.16%  (p=0.008 n=5+5)

name                       old 99%:ms     new 99%:ms     delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  3.84 ±20%      3.38 ± 3%     ~     (p=0.056 n=5+5)
_directGzipHead-2              4.01 ±14%      3.58 ± 8%     ~     (p=0.063 n=5+4)
_noDirectGzip-2                16.3 ± 9%      16.1 ± 6%     ~     (p=0.841 n=5+5)
_directGzip_decode-2           66.9 ±16%      70.6 ± 7%     ~     (p=0.310 n=5+5)
_noDirectGzip_decode-2         15.7 ± 6%      15.8 ± 4%     ~     (p=0.968 n=5+5)
_jsonBody-2                    7.87 ±32%      7.23 ±14%     ~     (p=0.841 n=5+5)
_jsonBodyValidation-2          8.41 ±16%      7.97 ±10%     ~     (p=0.548 n=5+5)
_outputHeaders-2               4.28 ±14%      3.98 ±14%     ~     (p=0.421 n=5+5)
_requestResponseMapping-2      7.54 ±17%      8.51 ±16%     ~     (p=0.222 n=5+5)
_validation-2                  7.65 ±30%      9.18 ±19%     ~     (p=0.056 n=5+5)
_noValidation-2                5.77 ± 9%      6.09 ±25%     ~     (p=1.000 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  3.46 ± 3%      5.01 ±90%  +44.65%  (p=0.032 n=5+5)
_directGzipHead-2              3.92 ±15%      4.00 ± 9%     ~     (p=0.548 n=5+5)
_noDirectGzip-2                15.0 ± 4%      16.0 ±11%     ~     (p=0.151 n=5+5)
_directGzip_decode-2           64.4 ±10%      63.8 ±10%     ~     (p=1.000 n=5+5)
_noDirectGzip_decode-2         15.5 ± 3%      16.2 ±16%     ~     (p=0.548 n=5+5)
_htmlResponse-2                6.19 ±12%      6.70 ±14%     ~     (p=0.222 n=5+5)
_jsonBodyManual-2              4.69 ±14%      4.72 ± 7%     ~     (p=0.841 n=5+5)
_jsonBody-2                    6.82 ± 7%      7.19 ±11%     ~     (p=0.421 n=5+5)
_jsonBodyValidation-2          7.75 ±10%      8.76 ±21%     ~     (p=0.151 n=5+5)
_outputHeaders-2               7.17 ±14%      7.06 ±12%     ~     (p=0.841 n=5+5)
_requestResponseMapping-2      7.32 ±28%      7.22 ± 8%     ~     (p=0.421 n=5+5)
_validation-2                  8.37 ±18%      8.00 ±18%     ~     (p=0.841 n=5+5)
_noValidation-2                6.18 ± 6%      6.73 ± 5%   +8.87%  (p=0.008 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 3.65 ± 5%      3.83 ± 9%     ~     (p=0.286 n=5+4)
_ok-2                          4.32 ±23%      4.33 ± 8%     ~     (p=0.841 n=5+5)
_invalidBody-2                 6.70 ±18%      7.30 ±16%     ~     (p=0.222 n=5+5)

name                       old 99.9%:ms   new 99.9%:ms   delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  7.40 ±61%      5.62 ±24%     ~     (p=0.548 n=5+5)
_directGzipHead-2              9.69 ±36%     9.54 ±102%     ~     (p=0.548 n=5+5)
_noDirectGzip-2                20.6 ±14%      22.0 ±27%     ~     (p=0.841 n=5+5)
_directGzip_decode-2           89.9 ±24%      85.2 ±13%     ~     (p=0.841 n=5+5)
_noDirectGzip_decode-2         21.8 ±20%      19.2 ± 1%     ~     (p=0.063 n=5+5)
_jsonBody-2                    13.5 ±20%      12.4 ±36%     ~     (p=0.421 n=5+5)
_jsonBodyValidation-2          14.6 ±16%      15.0 ±37%     ~     (p=1.000 n=5+5)
_outputHeaders-2               11.5 ±43%       9.1 ±38%     ~     (p=0.095 n=5+5)
_requestResponseMapping-2      12.0 ±29%      14.3 ± 6%     ~     (p=0.222 n=5+5)
_validation-2                  13.3 ±21%      14.5 ±11%     ~     (p=0.548 n=5+5)
_noValidation-2                12.0 ±25%      11.3 ±42%     ~     (p=0.841 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  7.41 ±56%     11.57 ±62%     ~     (p=0.151 n=5+5)
_directGzipHead-2              9.01 ±44%      9.57 ±14%     ~     (p=1.000 n=5+5)
_noDirectGzip-2                19.3 ± 8%      22.7 ±23%     ~     (p=0.222 n=5+5)
_directGzip_decode-2           84.2 ± 5%      84.9 ±10%     ~     (p=1.000 n=5+5)
_noDirectGzip_decode-2         22.6 ±26%      23.5 ±26%     ~     (p=0.841 n=5+5)
_htmlResponse-2                9.93 ±26%     10.43 ±19%     ~     (p=0.690 n=5+5)
_jsonBodyManual-2              9.61 ±61%      9.21 ±32%     ~     (p=1.000 n=5+5)
_jsonBody-2                    11.5 ±23%      11.4 ±29%     ~     (p=1.000 n=5+5)
_jsonBodyValidation-2          12.5 ±28%      13.7 ±25%     ~     (p=0.690 n=5+5)
_outputHeaders-2               13.8 ±39%      14.5 ±28%     ~     (p=0.548 n=5+5)
_requestResponseMapping-2      11.9 ±47%      12.7 ±23%     ~     (p=0.421 n=5+5)
_validation-2                  14.1 ±26%      15.3 ±28%     ~     (p=0.841 n=5+5)
_noValidation-2                9.88 ±29%     13.45 ±48%     ~     (p=0.095 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 6.93 ±79%      7.99 ±91%     ~     (p=0.421 n=5+5)
_ok-2                          9.77 ±41%     11.14 ±39%     ~     (p=0.841 n=5+5)
_invalidBody-2                 12.5 ±64%      14.0 ±35%     ~     (p=0.548 n=5+5)

name                       old B:rcvd/op  new B:rcvd/op  delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                   630 ± 0%       630 ± 0%     ~     (all equal)
_directGzipHead-2               174 ± 0%       174 ± 0%     ~     (all equal)
_noDirectGzip-2               1.03k ± 0%     1.03k ± 0%     ~     (all equal)
_directGzip_decode-2            630 ± 0%       630 ± 0%     ~     (all equal)
_noDirectGzip_decode-2        1.03k ± 0%     1.03k ± 0%     ~     (all equal)
_jsonBody-2                     199 ± 0%       199 ± 0%     ~     (all equal)
_jsonBodyValidation-2           185 ± 0%       185 ± 0%     ~     (all equal)
_outputHeaders-2                146 ± 0%       146 ± 0%     ~     (all equal)
_requestResponseMapping-2      94.0 ± 0%      94.0 ± 0%     ~     (all equal)
_validation-2                   168 ± 0%       168 ± 0%     ~     (all equal)
_noValidation-2                 168 ± 0%       168 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                   638 ± 0%       638 ± 0%     ~     (all equal)
_directGzipHead-2               182 ± 0%       182 ± 0%     ~     (all equal)
_noDirectGzip-2               1.04k ± 0%     1.04k ± 0%     ~     (all equal)
_directGzip_decode-2            638 ± 0%       638 ± 0%     ~     (all equal)
_noDirectGzip_decode-2        1.04k ± 0%     1.04k ± 0%     ~     (all equal)
_htmlResponse-2                 355 ± 0%       355 ± 0%     ~     (all equal)
_jsonBodyManual-2               207 ± 0%       207 ± 0%     ~     (all equal)
_jsonBody-2                     207 ± 0%       207 ± 0%     ~     (all equal)
_jsonBodyValidation-2           193 ± 0%       193 ± 0%     ~     (all equal)
_outputHeaders-2                214 ± 0%       214 ± 0%     ~     (all equal)
_requestResponseMapping-2       108 ± 0%       108 ± 0%     ~     (all equal)
_validation-2                   176 ± 0%       176 ± 0%     ~     (all equal)
_noValidation-2                 176 ± 0%       176 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                  322 ± 0%       322 ± 0%     ~     (all equal)
_ok-2                           344 ± 0%       344 ± 0%     ~     (p=0.444 n=5+5)
_invalidBody-2                  420 ± 0%       420 ± 0%     ~     (all equal)

name                       old B:sent/op  new B:sent/op  delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                   103 ± 0%       103 ± 0%     ~     (all equal)
_directGzipHead-2               104 ± 0%       104 ± 0%     ~     (all equal)
_noDirectGzip-2                 117 ± 0%       117 ± 0%     ~     (all equal)
_directGzip_decode-2            116 ± 0%       116 ± 0%     ~     (all equal)
_noDirectGzip_decode-2          130 ± 0%       130 ± 0%     ~     (all equal)
_jsonBody-2                     188 ± 0%       188 ± 0%     ~     (all equal)
_jsonBodyValidation-2           192 ± 0%       192 ± 0%     ~     (all equal)
_outputHeaders-2               77.0 ± 0%      77.0 ± 0%     ~     (all equal)
_requestResponseMapping-2       169 ± 0%       169 ± 0%     ~     (all equal)
_validation-2                   170 ± 0%       170 ± 0%     ~     (all equal)
_noValidation-2                 173 ± 0%       173 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                   103 ± 0%       103 ± 0%     ~     (all equal)
_directGzipHead-2               104 ± 0%       104 ± 0%     ~     (all equal)
_noDirectGzip-2                 117 ± 0%       117 ± 0%     ~     (all equal)
_directGzip_decode-2            116 ± 0%       116 ± 0%     ~     (all equal)
_noDirectGzip_decode-2          130 ± 0%       130 ± 0%     ~     (all equal)
_htmlResponse-2                 108 ± 0%       108 ± 0%     ~     (all equal)
_jsonBodyManual-2               195 ± 0%       195 ± 0%     ~     (all equal)
_jsonBody-2                     188 ± 0%       188 ± 0%     ~     (all equal)
_jsonBodyValidation-2           192 ± 0%       192 ± 0%     ~     (all equal)
_outputHeaders-2               88.0 ± 0%      88.0 ± 0%     ~     (all equal)
_requestResponseMapping-2       169 ± 0%       169 ± 0%     ~     (all equal)
_validation-2                   170 ± 0%       170 ± 0%     ~     (all equal)
_noValidation-2                 173 ± 0%       173 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 74.0 ± 0%      74.0 ± 0%     ~     (all equal)
_ok-2                          74.0 ± 0%      74.0 ± 0%     ~     (all equal)
_invalidBody-2                  137 ± 0%       137 ± 0%     ~     (all equal)

name                       old rps        new rps        delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                 39.6k ± 5%     41.9k ± 1%   +5.99%  (p=0.008 n=5+5)
_directGzipHead-2             39.6k ± 1%     40.3k ±12%     ~     (p=0.151 n=5+5)
_noDirectGzip-2               7.39k ± 1%     7.48k ± 2%     ~     (p=0.310 n=5+5)
_directGzip_decode-2          2.02k ± 4%     2.05k ± 5%     ~     (p=0.460 n=5+5)
_noDirectGzip_decode-2        7.36k ± 1%     7.42k ± 1%     ~     (p=0.095 n=5+5)
_jsonBody-2                   20.4k ± 0%     20.3k ± 3%     ~     (p=0.730 n=4+5)
_jsonBodyValidation-2         17.0k ± 3%     17.2k ± 2%     ~     (p=0.421 n=5+5)
_outputHeaders-2              39.2k ± 2%     39.9k ± 3%     ~     (p=0.222 n=5+5)
_requestResponseMapping-2     19.7k ± 3%     19.8k ± 1%     ~     (p=0.690 n=5+5)
_validation-2                 18.6k ± 3%     18.7k ± 1%     ~     (p=0.841 n=5+5)
_noValidation-2               26.8k ± 1%     26.4k ± 2%     ~     (p=0.222 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                 40.2k ± 3%     38.2k ±11%     ~     (p=0.056 n=5+5)
_directGzipHead-2             39.9k ± 3%     39.5k ± 1%     ~     (p=0.841 n=5+5)
_noDirectGzip-2               7.80k ± 1%     7.72k ± 1%     ~     (p=0.095 n=5+5)
_directGzip_decode-2          2.11k ± 2%     2.06k ± 2%   -2.43%  (p=0.032 n=5+5)
_noDirectGzip_decode-2        7.67k ± 1%     7.56k ± 6%     ~     (p=0.690 n=5+5)
_htmlResponse-2               23.8k ± 3%     22.9k ± 1%   -3.47%  (p=0.032 n=5+5)
_jsonBodyManual-2             33.1k ± 1%     32.8k ± 1%     ~     (p=0.222 n=5+5)
_jsonBody-2                   22.4k ± 2%     21.9k ± 3%     ~     (p=0.310 n=5+5)
_jsonBodyValidation-2         17.0k ± 2%     16.5k ± 3%   -3.12%  (p=0.016 n=5+5)
_outputHeaders-2              23.3k ± 3%     23.1k ± 3%     ~     (p=1.000 n=5+5)
_requestResponseMapping-2     19.8k ± 4%     19.3k ± 2%     ~     (p=0.151 n=5+5)
_validation-2                 18.4k ± 3%     17.9k ± 4%     ~     (p=0.095 n=5+5)
_noValidation-2               25.8k ± 2%     24.9k ± 2%   -3.57%  (p=0.008 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                37.4k ± 1%     35.1k ±12%   -6.31%  (p=0.008 n=5+5)
_ok-2                         36.5k ± 4%     35.2k ± 2%   -3.70%  (p=0.032 n=5+5)
_invalidBody-2                24.9k ± 2%     23.5k ± 1%   -5.61%  (p=0.008 n=5+5)

name                       old alloc/op   new alloc/op   delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                3.85kB ± 0%    3.85kB ± 0%     ~     (p=0.540 n=5+5)
_directGzipHead-2            3.85kB ± 0%    3.84kB ± 0%   -0.06%  (p=0.024 n=5+5)
_noDirectGzip-2              6.79kB ±15%    6.67kB ±18%     ~     (p=0.841 n=5+5)
_directGzip_decode-2          403kB ± 0%     403kB ± 0%     ~     (p=0.841 n=5+5)
_noDirectGzip_decode-2       5.81kB ± 7%    5.80kB ±12%     ~     (p=0.548 n=5+5)
_jsonBody-2                  13.1kB ± 0%    13.1kB ± 0%     ~     (p=0.444 n=5+5)
_jsonBodyValidation-2        18.9kB ± 0%    18.9kB ± 0%     ~     (p=0.889 n=5+5)
_outputHeaders-2             3.56kB ± 0%    3.56kB ± 0%     ~     (p=0.913 n=5+5)
_requestResponseMapping-2    16.6kB ± 0%    16.6kB ± 0%     ~     (p=1.000 n=5+5)
_validation-2                16.5kB ± 0%    16.5kB ± 0%     ~     (p=0.183 n=5+5)
_noValidation-2              7.75kB ± 0%    7.75kB ± 0%     ~     (p=0.238 n=5+5)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                3.88kB ± 0%    3.88kB ± 0%     ~     (p=0.714 n=5+5)
_directGzipHead-2            3.88kB ± 0%    3.88kB ± 0%     ~     (p=0.722 n=5+5)
_noDirectGzip-2              6.75kB ±19%    6.56kB ±19%     ~     (p=0.690 n=5+5)
_directGzip_decode-2          403kB ± 0%     403kB ± 0%     ~     (p=0.151 n=5+5)
_noDirectGzip_decode-2       5.90kB ± 8%    5.82kB ± 6%     ~     (p=0.690 n=5+5)
_htmlResponse-2              8.02kB ± 0%    8.03kB ± 0%   +0.04%  (p=0.040 n=5+5)
_jsonBodyManual-2            4.49kB ± 0%    4.49kB ± 0%     ~     (p=0.468 n=5+5)
_jsonBody-2                  10.2kB ± 0%    10.2kB ± 0%     ~     (p=0.627 n=5+5)
_jsonBodyValidation-2        19.0kB ± 0%    19.0kB ± 0%     ~     (p=0.937 n=4+5)
_outputHeaders-2             10.4kB ± 0%    10.4kB ± 0%     ~     (p=0.381 n=5+5)
_requestResponseMapping-2    16.6kB ± 0%    16.6kB ± 0%     ~     (p=1.000 n=5+5)
_validation-2                16.5kB ± 0%    16.5kB ± 0%     ~     (p=0.381 n=5+5)
_noValidation-2              7.81kB ± 0%    7.82kB ± 0%     ~     (p=0.063 n=5+5)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2               4.63kB ± 0%    4.63kB ± 0%     ~     (p=0.524 n=5+5)
_ok-2                        4.53kB ± 0%    4.53kB ± 0%     ~     (p=0.514 n=4+4)
_invalidBody-2               8.25kB ± 0%    8.25kB ± 0%     ~     (p=1.000 n=4+4)

name                       old allocs/op  new allocs/op  delta
pkg:github.com/swaggest/rest/_examples/advanced goos:linux goarch:amd64
_directGzip-2                  42.0 ± 0%      42.0 ± 0%     ~     (all equal)
_directGzipHead-2              42.0 ± 0%      42.0 ± 0%     ~     (all equal)
_noDirectGzip-2                49.6 ± 3%      49.8 ± 4%     ~     (p=1.000 n=5+5)
_directGzip_decode-2            504 ± 0%       504 ± 0%     ~     (p=1.000 n=5+5)
_noDirectGzip_decode-2         50.0 ± 0%      50.0 ± 0%     ~     (all equal)
_jsonBody-2                     128 ± 0%       128 ± 0%     ~     (all equal)
_jsonBodyValidation-2           185 ± 0%       185 ± 0%     ~     (all equal)
_outputHeaders-2               36.0 ± 0%      36.0 ± 0%     ~     (all equal)
_requestResponseMapping-2       124 ± 0%       124 ± 0%     ~     (all equal)
_validation-2                   154 ± 0%       154 ± 0%     ~     (all equal)
_noValidation-2                91.0 ± 0%      91.0 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/advanced-generic goos:linux goarch:amd64
_directGzip-2                  44.0 ± 0%      44.0 ± 0%     ~     (all equal)
_directGzipHead-2              44.0 ± 0%      44.0 ± 0%     ~     (all equal)
_noDirectGzip-2                51.6 ± 3%      51.6 ± 3%     ~     (p=1.000 n=5+5)
_directGzip_decode-2            506 ± 0%       506 ± 0%     ~     (p=0.397 n=5+5)
_noDirectGzip_decode-2         52.0 ± 0%      52.0 ± 0%     ~     (all equal)
_htmlResponse-2                 145 ± 0%       145 ± 0%     ~     (all equal)
_jsonBodyManual-2              49.0 ± 0%      49.0 ± 0%     ~     (all equal)
_jsonBody-2                     100 ± 0%       100 ± 0%     ~     (all equal)
_jsonBodyValidation-2           187 ± 0%       187 ± 0%     ~     (all equal)
_outputHeaders-2                113 ± 0%       113 ± 0%     ~     (all equal)
_requestResponseMapping-2       126 ± 0%       126 ± 0%     ~     (all equal)
_validation-2                   156 ± 0%       156 ± 0%     ~     (all equal)
_noValidation-2                93.0 ± 0%      93.0 ± 0%     ~     (all equal)
pkg:github.com/swaggest/rest/_examples/task-api/internal/infra/nethttp goos:linux goarch:amd64
_notFoundSrv-2                 53.0 ± 0%      53.0 ± 0%     ~     (all equal)
_ok-2                          49.0 ± 0%      49.0 ± 0%     ~     (all equal)
_invalidBody-2                 97.0 ± 0%      97.0 ± 0%     ~     (all equal)
```
</details>
<!-- Sticky Pull Request Commentbench-examples -->
github-actions[bot]: ### Unit Test Coverage
total:								(statements)			83.8%
changed lines: (statements) 95.2%
<details><summary>Coverage of changed lines</summary>

|          File          |         Function         | Coverage |
|------------------------|--------------------------|----------|
| Total                  |                          | 95.2%    |
| request/factory.go     |                          | 95.2%    |
| request/factory.go:53  | NewDecoderFactory        | 100.0%   |
| request/factory.go:74  | SetDecoderFunc           | 100.0%   |
| request/factory.go:98  | MakeDecoder              | 100.0%   |
| request/factory.go:295 | makeCustomMappingDecoder | 100.0%   |
| request/factory.go:254 | makeDefaultDecoder       | 89.7%    |

</details>

<details><summary>Coverage diff with base branch</summary>

|                    File                     |         Function         | Base Coverage | Current Coverage |
|---------------------------------------------|--------------------------|---------------|------------------|
| Total                                       |                          | 83.7%         | 83.8% (+0.1%)    |
| github.com/swaggest/rest/request/factory.go | makeCustomMappingDecoder | 93.3%         | 94.1% (+0.8%)    |
| github.com/swaggest/rest/request/factory.go | makeDefaultDecoder       | 100.0%        | 90.0% (-10.0%)   |

</details>
<!-- Sticky Pull Request Commentunit-test -->
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/swaggest/rest/pull/164?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) Report
> Merging [#164](https://app.codecov.io/gh/swaggest/rest/pull/164?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (e681f51) into [master](https://app.codecov.io/gh/swaggest/rest/commit/020a7389686279fdbde09ba59e5083c16864fb4a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (020a738) will **increase** coverage by `0.13%`.
> The diff coverage is `91.30%`.

```diff
@@            Coverage Diff             @@
##           master     #164      +/-   ##
==========================================
+ Coverage   78.04%   78.18%   +0.13%     
==========================================
  Files          28       28              
  Lines        1608     1627      +19     
==========================================
+ Hits         1255     1272      +17     
- Misses        232      233       +1     
- Partials      121      122       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `78.18% <91.30%> (+0.13%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://app.codecov.io/gh/swaggest/rest/pull/164?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) | Coverage Δ | |
|---|---|---|
| [request/factory.go](https://app.codecov.io/gh/swaggest/rest/pull/164?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-cmVxdWVzdC9mYWN0b3J5Lmdv) | `87.94% <91.30%> (+0.23%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest)


Patch:
diff --git a/_examples/advanced-generic/query_object.go b/_examples/advanced-generic/query_object.go
index 88bf7f8..148f404 100644
--- a/_examples/advanced-generic/query_object.go
+++ b/_examples/advanced-generic/query_object.go
@@ -9,16 +9,31 @@ import (
 )
 
 func queryObject() usecase.Interactor {
+	type jsonFilter struct {
+		Foo string `json:"foo" maxLength:"5"`
+	}
+
+	type deepObjectFilter struct {
+		Bar string  `json:"bar" query:"bar" minLength:"3"`
+		Baz *string `json:"baz,omitempty" query:"baz" minLength:"3"`
+	}
+
 	type inputQueryObject struct {
-		Query map[int]float64 `query:"in_query" description:"Object value in query."`
+		Query            map[int]float64  `query:"in_query" description:"Object value in query."`
+		JSONFilter       jsonFilter       `query:"json_filter" description:"JSON object value in query."`
+		DeepObjectFilter deepObjectFilter `query:"deep_object_filter" description:"Deep object value in query params."`
 	}
 
 	type outputQueryObject struct {
-		Query map[int]float64 `json:"inQuery"`
+		Query            map[int]float64  `json:"inQuery"`
+		JSONFilter       jsonFilter       `json:"jsonFilter"`
+		DeepObjectFilter deepObjectFilter `json:"deepObjectFilter"`
 	}
 
 	u := usecase.NewInteractor(func(ctx context.Context, in inputQueryObject, out *outputQueryObject) (err error) {
 		out.Query = in.Query
+		out.JSONFilter = in.JSONFilter
+		out.DeepObjectFilter = in.DeepObjectFilter
 
 		return nil
 	})
diff --git a/_examples/advanced-generic/router.go b/_examples/advanced-generic/router.go
index bfe11b9..7ec26ff 100644
--- a/_examples/advanced-generic/router.go
+++ b/_examples/advanced-generic/router.go
@@ -48,7 +48,7 @@ func NewRouter() http.Handler {
 		return usecase.Interact(func(ctx context.Context, input, output interface{}) error {
 			err := next.Interact(ctx, input, output)
 			if err != nil && err != rest.HTTPCodeAsError(http.StatusNotModified) {
-				log.Printf("usecase %s request (%v) failed: %v\n", name, input, err)
+				log.Printf("usecase %s request (%+v) failed: %v\n", name, input, err)
 			}
 
 			return err
diff --git a/_examples/advanced/router.go b/_examples/advanced/router.go
index ffbd878..fd5335b 100644
--- a/_examples/advanced/router.go
+++ b/_examples/advanced/router.go
@@ -14,7 +14,7 @@ import (
 	"github.com/swaggest/rest/response"
 	"github.com/swaggest/rest/response/gzip"
 	"github.com/swaggest/rest/web"
-	swgui "github.com/swaggest/swgui/v4emb"
+	swgui "github.com/swaggest/swgui/v5emb"
 )
 
 func NewRouter() http.Handler {
diff --git a/_examples/basic/main.go b/_examples/basic/main.go
index 368d25f..3f209f1 100644
--- a/_examples/basic/main.go
+++ b/_examples/basic/main.go
@@ -10,7 +10,7 @@ import (
 
 	"github.com/swaggest/rest/response/gzip"
 	"github.com/swaggest/rest/web"
-	swgui "github.com/swaggest/swgui/v4emb"
+	swgui "github.com/swaggest/swgui/v5emb"
 	"github.com/swaggest/usecase"
 	"github.com/swaggest/usecase/status"
 )
diff --git a/_examples/generic/main.go b/_examples/generic/main.go
index 15f8165..eb839f6 100644
--- a/_examples/generic/main.go
+++ b/_examples/generic/main.go
@@ -13,7 +13,7 @@ import (
 
 	"github.com/swaggest/rest/response/gzip"
 	"github.com/swaggest/rest/web"
-	swgui "github.com/swaggest/swgui/v4emb"
+	swgui "github.com/swaggest/swgui/v5emb"
 	"github.com/swaggest/usecase"
 	"github.com/swaggest/usecase/status"
 )
diff --git a/_examples/go.mod b/_examples/go.mod
index e41166c..71723ae 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -6,17 +6,17 @@ replace github.com/swaggest/rest => ../
 
 require (
 	github.com/bool64/ctxd v1.2.1
-	github.com/bool64/dev v0.2.28
+	github.com/bool64/dev v0.2.29
 	github.com/bool64/httpmock v0.1.13
 	github.com/bool64/httptestbench v0.1.4
-	github.com/go-chi/chi/v5 v5.0.8
+	github.com/go-chi/chi/v5 v5.0.10
 	github.com/kelseyhightower/envconfig v1.4.0
 	github.com/rs/cors v1.9.0
-	github.com/stretchr/testify v1.8.2
-	github.com/swaggest/assertjson v1.8.1
+	github.com/stretchr/testify v1.8.4
+	github.com/swaggest/assertjson v1.9.0
 	github.com/swaggest/jsonschema-go v0.3.52
-	github.com/swaggest/openapi-go v0.2.30
-	github.com/swaggest/swgui v1.6.2
+	github.com/swaggest/openapi-go v0.2.33
+	github.com/swaggest/swgui v1.6.4
 	github.com/swaggest/usecase v1.2.1
 	github.com/valyala/fasthttp v1.46.0
 )
@@ -32,13 +32,13 @@ require (
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/fsnotify/fsnotify v1.5.4 // indirect
-	github.com/iancoleman/orderedmap v0.2.0 // indirect
+	github.com/iancoleman/orderedmap v0.3.0 // indirect
 	github.com/klauspost/compress v1.16.5 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/santhosh-tekuri/jsonschema/v3 v3.1.0 // indirect
 	github.com/sergi/go-diff v1.3.1 // indirect
-	github.com/swaggest/form/v5 v5.0.4 // indirect
-	github.com/swaggest/refl v1.1.0 // indirect
+	github.com/swaggest/form/v5 v5.1.1 // indirect
+	github.com/swaggest/refl v1.2.0 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
 	github.com/vearutop/dynhist-go v1.1.0 // indirect
 	github.com/vearutop/statigz v1.3.0 // indirect
diff --git a/_examples/go.sum b/_examples/go.sum
index f305c9c..c8044d9 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -4,8 +4,8 @@ github.com/bool64/ctxd v1.2.1 h1:hARFteq0zdn4bwfmxLhak3fXFuvtJVKDH2X29VV/2ls=
 github.com/bool64/ctxd v1.2.1/go.mod h1:ZG6QkeGVLTiUl2mxPpyHmFhDzFZCyocr9hluBV3LYuc=
 github.com/bool64/dev v0.2.5/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
 github.com/bool64/dev v0.2.25/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
-github.com/bool64/dev v0.2.28 h1:6ayDfrB/jnNr2iQAZHI+uT3Qi6rErSbJYQs1y8rSrwM=
-github.com/bool64/dev v0.2.28/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
+github.com/bool64/dev v0.2.29 h1:x+syGyh+0eWtOzQ1ItvLzOGIWyNWnyjXpHIcpF2HvL4=
+github.com/bool64/dev v0.2.29/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/httpmock v0.1.13 h1:3QpRXQ5kwHLW8xnVT8+Ug7VS6RerhdEFV+RWYC61aVo=
 github.com/bool64/httpmock v0.1.13/go.mod h1:YMTLaypQ3o5DAx78eA/kDRSLec0f+42sLMDmHdmeY+E=
 github.com/bool64/httptestbench v0.1.4 h1:35f9RwWqcnQRXM+sA+GUhWVGSa6XEFlKpNSH9oYzOjI=
@@ -19,12 +19,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
-github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
-github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
+github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
-github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
+github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
+github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
 github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
 github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
@@ -48,25 +48,21 @@ github.com/santhosh-tekuri/jsonschema/v3 v3.1.0/go.mod h1:8kzK2TC0k0YjOForaAHdNE
 github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
 github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/swaggest/assertjson v1.8.1 h1:Be2EHY9S2qwKWV+xWZB747Cd7Y79YK6JLdeyrgFvyMo=
-github.com/swaggest/assertjson v1.8.1/go.mod h1:/8kNRmDZAZfavS5VeWYtCimLGebn0Ak1/iErFUi+DEM=
-github.com/swaggest/form/v5 v5.0.4 h1:hTg+gjUEZG0vgsDdXOPi7I0Xu5OowXKoLZIGYZsoddg=
-github.com/swaggest/form/v5 v5.0.4/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
+github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
+github.com/swaggest/form/v5 v5.1.1 h1:ct6/rOQBGrqWUQ0FUv3vW5sHvTUb31AwTUWj947N6cY=
+github.com/swaggest/form/v5 v5.1.1/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
 github.com/swaggest/jsonschema-go v0.3.52 h1:cFk0Ma34MuZvA+JJ8S5WuQMedwxzUa+9+qrGwtoE39U=
 github.com/swaggest/jsonschema-go v0.3.52/go.mod h1:sRly7iaIIvbheAqsyvnKU3H7ogWbF0wtUBXyGpRXNm4=
-github.com/swaggest/openapi-go v0.2.30 h1:Gt7OsuBZ1rwmOWpwhzDuP1s6egoWpArIICCAi/3fcQo=
-github.com/swaggest/openapi-go v0.2.30/go.mod h1:H7ZYe1c/BuYH7xBgw8OmpHKnTmBJC2XeSGf49KvSaRg=
-github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc=
-github.com/swaggest/refl v1.1.0/go.mod h1:g3Qa6ki0A/L2yxiuUpT+cuBURuRaltF5SDQpg1kMZSY=
-github.com/swaggest/swgui v1.6.2 h1:DR+ioYt11YrXMaEmLcgaOEFSZ/8QW30uYYE/Ck41cPA=
-github.com/swaggest/swgui v1.6.2/go.mod h1:pydZ1eCyPtDKqARCWsVeUEzwRzFhpc6vylvDto3SWCM=
+github.com/swaggest/openapi-go v0.2.33 h1:5S9XCSuaex9KkLQVNiH0SYukiTEWCa2z66k7QQ+UUiQ=
+github.com/swaggest/openapi-go v0.2.33/go.mod h1:h7dk9ApzrU9yJ7O8TSoW+a1PyT7R+bnYHDw/NpexlYA=
+github.com/swaggest/refl v1.2.0 h1:Qqqhfwi7REXF6/4cwJmj3gQMzl0Q0cYquxTYdD0kvi0=
+github.com/swaggest/refl v1.2.0/go.mod h1:CkC6g7h1PW33KprTuYRSw8UUOslRUt4lF3oe7tTIgNU=
+github.com/swaggest/swgui v1.6.4 h1:9G7HTUMOAu/Y9YF2wDvoq9GXFlV4BH5y8BSOl4MWfl8=
+github.com/swaggest/swgui v1.6.4/go.mod h1:xsfGb4NtnBspBXKXtlPdVrvoqzCIZ338Aj3tHNikz2Q=
 github.com/swaggest/usecase v1.2.1 h1:XYVdK9tK2KCPglTflUi7aWBrVwIyb58D5mvGWED7pNs=
 github.com/swaggest/usecase v1.2.1/go.mod h1:5ccwVsLJ9eQpU4m0AGTM444pdqSPQBiocIwMmdRH9lQ=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -96,6 +92,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/mount/main.go b/_examples/mount/main.go
index 8ea6ba1..ccfc861 100644
--- a/_examples/mount/main.go
+++ b/_examples/mount/main.go
@@ -9,7 +9,7 @@ import (
 	"github.com/go-chi/chi/v5/middleware"
 	"github.com/swaggest/rest/nethttp"
 	"github.com/swaggest/rest/web"
-	swgui "github.com/swaggest/swgui/v4emb"
+	swgui "github.com/swaggest/swgui/v5emb"
 	"github.com/swaggest/usecase"
 )
 
diff --git a/_examples/task-api/internal/infra/nethttp/router.go b/_examples/task-api/internal/infra/nethttp/router.go
index 2509796..5a7551e 100644
--- a/_examples/task-api/internal/infra/nethttp/router.go
+++ b/_examples/task-api/internal/infra/nethttp/router.go
@@ -13,7 +13,7 @@ import (
 	"github.com/swaggest/rest/_examples/task-api/internal/usecase"
 	"github.com/swaggest/rest/nethttp"
 	"github.com/swaggest/rest/web"
-	swgui "github.com/swaggest/swgui/v4emb"
+	swgui "github.com/swaggest/swgui/v5emb"
 )
 
 // NewRouter creates HTTP router.
diff --git a/go.mod b/go.mod
index 065f8fe..471e9a6 100644
--- a/go.mod
+++ b/go.mod
@@ -3,24 +3,24 @@ module github.com/swaggest/rest
 go 1.17
 
 require (
-	github.com/bool64/dev v0.2.28
+	github.com/bool64/dev v0.2.29
 	github.com/bool64/httpmock v0.1.13
 	github.com/bool64/shared v0.1.5
 	github.com/cespare/xxhash/v2 v2.2.0
-	github.com/go-chi/chi/v5 v5.0.8
+	github.com/go-chi/chi/v5 v5.0.10
 	github.com/santhosh-tekuri/jsonschema/v3 v3.1.0
 	github.com/stretchr/testify v1.8.2
-	github.com/swaggest/assertjson v1.8.1
-	github.com/swaggest/form/v5 v5.0.4
+	github.com/swaggest/assertjson v1.9.0
+	github.com/swaggest/form/v5 v5.1.1
 	github.com/swaggest/jsonschema-go v0.3.52
-	github.com/swaggest/openapi-go v0.2.30
-	github.com/swaggest/refl v1.1.0
+	github.com/swaggest/openapi-go v0.2.33
+	github.com/swaggest/refl v1.2.0
 	github.com/swaggest/usecase v1.2.1
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/iancoleman/orderedmap v0.2.0 // indirect
+	github.com/iancoleman/orderedmap v0.3.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/sergi/go-diff v1.3.1 // indirect
 	github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff // indirect
diff --git a/go.sum b/go.sum
index 3269847..5b4e3de 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,9 @@ github.com/bool64/dev v0.2.17/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8
 github.com/bool64/dev v0.2.24/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/dev v0.2.25/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/dev v0.2.27/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
-github.com/bool64/dev v0.2.28 h1:6ayDfrB/jnNr2iQAZHI+uT3Qi6rErSbJYQs1y8rSrwM=
 github.com/bool64/dev v0.2.28/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
+github.com/bool64/dev v0.2.29 h1:x+syGyh+0eWtOzQ1ItvLzOGIWyNWnyjXpHIcpF2HvL4=
+github.com/bool64/dev v0.2.29/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
 github.com/bool64/httpmock v0.1.13 h1:3QpRXQ5kwHLW8xnVT8+Ug7VS6RerhdEFV+RWYC61aVo=
 github.com/bool64/httpmock v0.1.13/go.mod h1:YMTLaypQ3o5DAx78eA/kDRSLec0f+42sLMDmHdmeY+E=
 github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
@@ -17,8 +18,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
-github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
+github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -31,8 +32,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
 github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
+github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
+github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -69,18 +71,18 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/swaggest/assertjson v1.8.0/go.mod h1:/8kNRmDZAZfavS5VeWYtCimLGebn0Ak1/iErFUi+DEM=
-github.com/swaggest/assertjson v1.8.1 h1:Be2EHY9S2qwKWV+xWZB747Cd7Y79YK6JLdeyrgFvyMo=
 github.com/swaggest/assertjson v1.8.1/go.mod h1:/8kNRmDZAZfavS5VeWYtCimLGebn0Ak1/iErFUi+DEM=
-github.com/swaggest/form/v5 v5.0.4 h1:hTg+gjUEZG0vgsDdXOPi7I0Xu5OowXKoLZIGYZsoddg=
-github.com/swaggest/form/v5 v5.0.4/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
-github.com/swaggest/jsonschema-go v0.3.50/go.mod h1:QfUB5HaZ8y5TiFtCPhM7QwvPNKxTsYxDJaLHTLq6jgU=
+github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
+github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
+github.com/swaggest/form/v5 v5.1.1 h1:ct6/rOQBGrqWUQ0FUv3vW5sHvTUb31AwTUWj947N6cY=
+github.com/swaggest/form/v5 v5.1.1/go.mod h1:X1hraaoONee20PMnGNLQpO32f9zbQ0Czfm7iZThuEKg=
 github.com/swaggest/jsonschema-go v0.3.52 h1:cFk0Ma34MuZvA+JJ8S5WuQMedwxzUa+9+qrGwtoE39U=
 github.com/swaggest/jsonschema-go v0.3.52/go.mod h1:sRly7iaIIvbheAqsyvnKU3H7ogWbF0wtUBXyGpRXNm4=
-github.com/swaggest/openapi-go v0.2.30 h1:Gt7OsuBZ1rwmOWpwhzDuP1s6egoWpArIICCAi/3fcQo=
-github.com/swaggest/openapi-go v0.2.30/go.mod h1:H7ZYe1c/BuYH7xBgw8OmpHKnTmBJC2XeSGf49KvSaRg=
-github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc=
+github.com/swaggest/openapi-go v0.2.33 h1:5S9XCSuaex9KkLQVNiH0SYukiTEWCa2z66k7QQ+UUiQ=
+github.com/swaggest/openapi-go v0.2.33/go.mod h1:h7dk9ApzrU9yJ7O8TSoW+a1PyT7R+bnYHDw/NpexlYA=
 github.com/swaggest/refl v1.1.0/go.mod h1:g3Qa6ki0A/L2yxiuUpT+cuBURuRaltF5SDQpg1kMZSY=
+github.com/swaggest/refl v1.2.0 h1:Qqqhfwi7REXF6/4cwJmj3gQMzl0Q0cYquxTYdD0kvi0=
+github.com/swaggest/refl v1.2.0/go.mod h1:CkC6g7h1PW33KprTuYRSw8UUOslRUt4lF3oe7tTIgNU=
 github.com/swaggest/usecase v1.2.1 h1:XYVdK9tK2KCPglTflUi7aWBrVwIyb58D5mvGWED7pNs=
 github.com/swaggest/usecase v1.2.1/go.mod h1:5ccwVsLJ9eQpU4m0AGTM444pdqSPQBiocIwMmdRH9lQ=
 github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff h1:7YqG491bE4vstXRz1lD38rbSgbXnirvROz1lZiOnPO8=
diff --git a/jsonschema/validator.go b/jsonschema/validator.go
index fa2caf5..dc9f945 100644
--- a/jsonschema/validator.go
+++ b/jsonschema/validator.go
@@ -214,7 +214,7 @@ func (v *Validator) ValidateData(in rest.ParamIn, namedData map[string]interface
 					errs = make(rest.ValidationErrors, 1)
 				}
 
-				errs[string(in)+":"+name] = []string{fmt.Sprintf("unknown parameter with value %v", value)}
+				errs[string(in)+":"+name] = []string{fmt.Sprintf("unknown parameter with value %+v", value)}
 			}
 
 			continue
diff --git a/openapi/collector.go b/openapi/collector.go
index b3ff2e9..c4b5dc6 100644
--- a/openapi/collector.go
+++ b/openapi/collector.go
@@ -367,12 +367,27 @@ func (c *Collector) provideParametersJSONSchemas(op openapi3.Operation, validato
 	}
 
 	for _, p := range op.Parameters {
-		if p.Parameter.Schema == nil {
+		pp := p.Parameter
+
+		required := false
+		if pp.Required != nil && *pp.Required {
+			required = true
+		}
+
+		sc := paramSchema(pp)
+
+		if sc == nil {
+			if validator != nil {
+				err := validator.AddSchema(rest.ParamIn(pp.In), pp.Name, nil, required)
+				if err != nil {
+					return fmt.Errorf("failed to add validation schema for parameter (%s, %s): %w", pp.In, pp.Name, err)
+				}
+			}
+
 			continue
 		}
 
-		pp := p.Parameter
-		schema := pp.Schema.ToJSONSchema(c.Reflector().Spec)
+		schema := sc.ToJSONSchema(c.Reflector().Spec)
 
 		var (
 			err        error
@@ -386,11 +401,6 @@ func (c *Collector) provideParametersJSONSchemas(op openapi3.Operation, validato
 			}
 		}
 
-		required := false
-		if pp.Required != nil && *pp.Required {
-			required = true
-		}
-
 		if validator != nil {
 			err = validator.AddSchema(rest.ParamIn(pp.In), pp.Name, schemaData, required)
 			if err != nil {
@@ -402,6 +412,18 @@ func (c *Collector) provideParametersJSONSchemas(op openapi3.Operation, validato
 	return nil
 }
 
+func paramSchema(p *openapi3.Parameter) *openapi3.SchemaOrRef {
+	sc := p.Schema
+
+	if sc == nil {
+		if jsc, ok := p.Content["application/json"]; ok {
+			sc = jsc.Schema
+		}
+	}
+
+	return sc
+}
+
 // ProvideRequestJSONSchemas provides JSON Schemas for request structure.
 func (c *Collector) ProvideRequestJSONSchemas(
 	method string,
diff --git a/request/decoder.go b/request/decoder.go
index fd14f18..92ef627 100644
--- a/request/decoder.go
+++ b/request/decoder.go
@@ -39,6 +39,10 @@ func decodeValidate(d *form.Decoder, v interface{}, p url.Values, in rest.ParamI
 
 	if len(p) > len(goValues) {
 		for k := range p {
+			if pos := strings.Index(k, "["); pos > 0 {
+				k = k[:pos]
+			}
+
 			if _, exists := goValues[k]; !exists {
 				pk := p[k]
 				switch len(pk) {
diff --git a/request/factory.go b/request/factory.go
index a250af3..a669730 100644
--- a/request/factory.go
+++ b/request/factory.go
@@ -59,6 +59,8 @@ func NewDecoderFactory() *DecoderFactory {
 	df.SetDecoderFunc("form", formToURLValues)
 
 	defaultValDecoder := form.NewDecoder()
+	defaultValDecoder.SetNamespacePrefix("[")
+	defaultValDecoder.SetNamespaceSuffix("]")
 	defaultValDecoder.RegisterTagNameFunc(func(field reflect.StructField) string {
 		return field.Name
 	})
@@ -80,6 +82,8 @@ func (df *DecoderFactory) SetDecoderFunc(tagName rest.ParamIn, d func(r *http.Re
 
 	df.decoderFunctions[tagName] = d
 	dec := form.NewDecoder()
+	dec.SetNamespacePrefix("[")
+	dec.SetNamespaceSuffix("]")
 	dec.SetTagName(string(tagName))
 	dec.SetMode(form.ModeExplicit)
 	df.formDecoders[tagName] = dec
@@ -98,7 +102,7 @@ func (df *DecoderFactory) MakeDecoder(
 ) nethttp.RequestDecoder {
 	d := initDecoder(input)
 
-	if df.ApplyDefaults && refl.HasTaggedFields(input, defaultTag) {
+	if df.ApplyDefaults {
 		df.makeDefaultDecoder(input, &d)
 	}
 
@@ -225,7 +229,7 @@ func (df *DecoderFactory) jsonParams(formDecoder *form.Decoder, in rest.ParamIn,
 
 		fieldVal := v.Interface()
 
-		if refl.HasTaggedFields(fieldVal, jsonTag) {
+		if refl.HasTaggedFields(fieldVal, jsonTag) && !refl.HasTaggedFields(fieldVal, string(in)) {
 			// If value is a struct with `json` tags, custom decoder unmarshals json
 			// from a string value into a struct.
 			formDecoder.RegisterFunc(func(s string) (interface{}, error) {
@@ -250,9 +254,35 @@ func (df *DecoderFactory) jsonParams(formDecoder *form.Decoder, in rest.ParamIn,
 func (df *DecoderFactory) makeDefaultDecoder(input interface{}, m *decoder) {
 	defaults := url.Values{}
 
-	refl.WalkTaggedFields(reflect.ValueOf(input), func(v reflect.Value, sf reflect.StructField, tag string) {
-		defaults[sf.Name] = []string{tag}
-	}, defaultTag)
+	refl.WalkFieldsRecursively(reflect.ValueOf(input), func(v reflect.Value, sf reflect.StructField, path []reflect.StructField) {
+		var key string
+
+		for _, p := range path {
+			if p.Anonymous {
+				continue
+			}
+
+			if key == "" {
+				key = p.Name
+			} else {
+				key += "[" + p.Name + "]"
+			}
+		}
+
+		if key == "" {
+			key = sf.Name
+		} else {
+			key += "[" + sf.Name + "]"
+		}
+
+		if d, ok := sf.Tag.Lookup(defaultTag); ok {
+			defaults[key] = []string{d}
+		}
+	})
+
+	if len(defaults) == 0 {
+		return
+	}
 
 	dec := df.defaultValDecoder
 
@@ -265,6 +295,8 @@ func (df *DecoderFactory) makeDefaultDecoder(input interface{}, m *decoder) {
 func (df *DecoderFactory) makeCustomMappingDecoder(customMapping rest.RequestMapping, m *decoder) {
 	for in, mapping := range customMapping {
 		dec := form.NewDecoder()
+		dec.SetNamespacePrefix("[")
+		dec.SetNamespaceSuffix("]")
 		dec.SetTagName(string(in))
 
 		// Copy mapping to avoid mutability.


Test patch:
diff --git a/_examples/advanced-generic/_testdata/openapi.json b/_examples/advanced-generic/_testdata/openapi.json
index 250f48d..d9bb1f0 100644
--- a/_examples/advanced-generic/_testdata/openapi.json
+++ b/_examples/advanced-generic/_testdata/openapi.json
@@ -308,7 +308,7 @@
           },
           {
             "name":"identity","in":"query","description":"JSON value in query",
-            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedJSONPayload"}}}
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryAdvancedJSONPayload"}}}
           },
           {
             "name":"in-path","in":"path","description":"Simple scalar value in path","required":true,
@@ -316,7 +316,7 @@
           },
           {
             "name":"in_cookie","in":"cookie","description":"UUID in cookie.",
-            "schema":{"$ref":"#/components/schemas/UuidUUID"}
+            "schema":{"$ref":"#/components/schemas/CookieUuidUUID"}
           },
           {
             "name":"X-Header","in":"header","description":"Simple scalar value in header.",
@@ -488,6 +488,14 @@
           {
             "name":"in_query","in":"query","description":"Object value in query.","style":"deepObject","explode":true,
             "schema":{"type":"object","additionalProperties":{"type":"number"},"description":"Object value in query."}
+          },
+          {
+            "name":"json_filter","in":"query","description":"JSON object value in query.",
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryAdvancedJsonFilter"}}}
+          },
+          {
+            "name":"deep_object_filter","in":"query","description":"Deep object value in query params.",
+            "style":"deepObject","explode":true,"schema":{"$ref":"#/components/schemas/QueryAdvancedDeepObjectFilter"}
           }
         ],
         "responses":{
@@ -622,6 +630,10 @@
     "schemas":{
       "AdvancedAnotherErr":{"type":"object","properties":{"foo":{"type":"integer"}}},
       "AdvancedCustomErr":{"type":"object","properties":{"details":{"type":"object","additionalProperties":{}},"msg":{"type":"string"}}},
+      "AdvancedDeepObjectFilter":{
+        "type":"object",
+        "properties":{"bar":{"minLength":3,"type":"string"},"baz":{"minLength":3,"type":"string","nullable":true}}
+      },
       "AdvancedGzipPassThroughStruct":{
         "type":"object",
         "properties":{"id":{"type":"integer"},"text":{"type":"array","items":{"type":"string"},"nullable":true}}
@@ -682,10 +694,10 @@
         "additionalProperties":false
       },
       "AdvancedJSONMapPayload":{"type":"object","additionalProperties":{"type":"number"}},
-      "AdvancedJSONPayload":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
       "AdvancedJSONPayloadType2":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
       "AdvancedJSONPayloadType3":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
       "AdvancedJSONSlicePayload":{"type":"array","items":{"type":"integer"},"nullable":true},
+      "AdvancedJsonFilter":{"type":"object","properties":{"foo":{"maxLength":5,"type":"string"}}},
       "AdvancedJsonMapReq":{"type":"object","additionalProperties":{"type":"number"}},
       "AdvancedJsonOutput":{
         "type":"object",
@@ -711,7 +723,11 @@
       "AdvancedOutputPortType3":{"type":"object","properties":{"data":{"type":"object","properties":{"value":{"type":"string"}}}}},
       "AdvancedOutputQueryObject":{
         "type":"object",
-        "properties":{"inQuery":{"type":"object","additionalProperties":{"type":"number"},"nullable":true}}
+        "properties":{
+          "deepObjectFilter":{"$ref":"#/components/schemas/AdvancedDeepObjectFilter"},
+          "inQuery":{"type":"object","additionalProperties":{"type":"number"},"nullable":true},
+          "jsonFilter":{"$ref":"#/components/schemas/AdvancedJsonFilter"}
+        }
       },
       "AdvancedOutputType2":{"type":"object","properties":{"path":{"type":"string"},"query":{"type":"integer"},"text":{"type":"string"}}},
       "AdvancedOutputType3":{"type":"object","properties":{"path":{"type":"string"},"query":{"type":"integer"},"text":{"type":"string"}}},
@@ -744,6 +760,7 @@
           "name":{"minLength":3,"type":"string"}
         }
       },
+      "CookieUuidUUID":{"type":"string","format":"uuid","example":"248df4b7-aa70-47b8-a036-33ac447e668d"},
       "FormDataAdvancedForm":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
       "FormDataAdvancedInputPort":{
         "required":["val2"],"type":"object",
@@ -776,6 +793,13 @@
       },
       "FormDataMultipartFile":{"type":"string","format":"binary","nullable":true},
       "FormDataMultipartFileHeader":{"type":"string","format":"binary","nullable":true},
+      "QueryAdvancedDeepObjectFilter":{
+        "type":"object",
+        "properties":{"bar":{"minLength":3,"type":"string"},"baz":{"minLength":3,"type":"string","nullable":true}},
+        "additionalProperties":false
+      },
+      "QueryAdvancedJSONPayload":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
+      "QueryAdvancedJsonFilter":{"type":"object","properties":{"foo":{"maxLength":5,"type":"string"}},"additionalProperties":false},
       "RestErrResponse":{
         "type":"object",
         "properties":{
@@ -785,8 +809,7 @@
           "status":{"type":"string","description":"Status text."}
         }
       },
-      "TextprotoMIMEHeader":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},
-      "UuidUUID":{"type":"string","format":"uuid","example":"248df4b7-aa70-47b8-a036-33ac447e668d"}
+      "TextprotoMIMEHeader":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}
     },
     "securitySchemes":{"User":{"type":"apiKey","name":"sessid","in":"cookie"}}
   }
diff --git a/_examples/advanced-generic/query_object_test.go b/_examples/advanced-generic/query_object_test.go
new file mode 100644
index 0000000..cf95747
--- /dev/null
+++ b/_examples/advanced-generic/query_object_test.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"encoding/json"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/swaggest/assertjson"
+)
+
+func Test_queryObject(t *testing.T) {
+	r := NewRouter()
+
+	srv := httptest.NewServer(r)
+	defer srv.Close()
+
+	for _, tc := range []struct {
+		name string
+		url  string
+		code int
+		resp string
+	}{
+		{
+			name: "validation_failed_deep_object",
+			url:  `/query-object?in_query[1]=0&in_query[2]=0&in_query[3]=0&json_filter={"foo":"strin"}&deep_object_filter[bar]=sd`,
+			code: http.StatusBadRequest,
+			resp: `{
+			  "msg":"invalid argument: validation failed",
+			  "details":{"query:deep_object_filter":["#/bar: length must be \u003e= 3, but got 2"]}
+			}`,
+		},
+		{
+			name: "validation_failed_deep_object_2",
+			url:  `/query-object?in_query[1]=0&in_query[2]=0&in_query[3]=0&json_filter={"foo":"strin"}&deep_object_filter[bar]=asd&deep_object_filter[baz]=sd`,
+			code: http.StatusBadRequest,
+			resp: `{
+			  "msg":"invalid argument: validation failed",
+			  "details":{"query:deep_object_filter":["#/baz: length must be \u003e= 3, but got 2"]}
+			}`,
+		},
+		{
+			name: "validation_failed_json",
+			url:  `/query-object?in_query[1]=0&in_query[2]=0&in_query[3]=0&json_filter={"foo":"string"}&deep_object_filter[bar]=asd`,
+			code: http.StatusBadRequest,
+			resp: `{
+			  "msg":"invalid argument: validation failed",
+			  "details":{"query:json_filter":["#/foo: length must be \u003c= 5, but got 6"]}
+			}`,
+		},
+		{
+			name: "ok",
+			url:  `/query-object?in_query[1]=0&in_query[2]=0&in_query[3]=0&json_filter={"foo":"strin"}&deep_object_filter[bar]=asd`,
+			code: http.StatusOK,
+			resp: `{
+			  "inQuery":{"1":0,"2":0,"3":0},"jsonFilter":{"foo":"strin"},
+			  "deepObjectFilter":{"bar":"asd"}
+			}`,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			req, err := http.NewRequest(
+				http.MethodGet,
+				srv.URL+tc.url,
+				nil,
+			)
+			require.NoError(t, err)
+
+			resp, err := http.DefaultTransport.RoundTrip(req)
+			require.NoError(t, err)
+
+			body, err := io.ReadAll(resp.Body)
+			assert.NoError(t, err)
+			assert.NoError(t, resp.Body.Close())
+			assertjson.EqMarshal(t, tc.resp, json.RawMessage(body))
+			assert.Equal(t, tc.code, resp.StatusCode)
+		})
+	}
+}
diff --git a/_examples/advanced/_testdata/openapi.json b/_examples/advanced/_testdata/openapi.json
index 37a6d36..e6d9712 100644
--- a/_examples/advanced/_testdata/openapi.json
+++ b/_examples/advanced/_testdata/openapi.json
@@ -242,7 +242,7 @@
           },
           {
             "name":"identity","in":"query","description":"JSON value in query",
-            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedJSONPayload"}}}
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryAdvancedJSONPayload"}}}
           },
           {
             "name":"in-path","in":"path","description":"Simple scalar value in path","required":true,
@@ -250,7 +250,7 @@
           },
           {
             "name":"in_cookie","in":"cookie","description":"UUID in cookie.",
-            "schema":{"$ref":"#/components/schemas/UuidUUID"}
+            "schema":{"$ref":"#/components/schemas/CookieUuidUUID"}
           },
           {
             "name":"X-Header","in":"header","description":"Simple scalar value in header.",
@@ -518,7 +518,6 @@
         "additionalProperties":false
       },
       "AdvancedJSONMapPayload":{"type":"object","additionalProperties":{"type":"number"}},
-      "AdvancedJSONPayload":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
       "AdvancedJSONPayloadType2":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
       "AdvancedJSONSlicePayload":{"type":"array","items":{"type":"integer"},"nullable":true},
       "AdvancedJsonMapReq":{"type":"object","additionalProperties":{"type":"number"},"nullable":true},
@@ -569,6 +568,7 @@
           "name":{"minLength":3,"type":"string"}
         }
       },
+      "CookieUuidUUID":{"type":"string","format":"uuid","example":"248df4b7-aa70-47b8-a036-33ac447e668d"},
       "FormDataAdvancedInputPort":{
         "required":["val2"],"type":"object",
         "properties":{"val2":{"minimum":3,"type":"integer","description":"Simple scalar value with sample validation."}},
@@ -600,6 +600,7 @@
       },
       "FormDataMultipartFile":{"type":"string","format":"binary","nullable":true},
       "FormDataMultipartFileHeader":{"type":"string","format":"binary","nullable":true},
+      "QueryAdvancedJSONPayload":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}},"additionalProperties":false},
       "RestErrResponse":{
         "type":"object",
         "properties":{
@@ -609,8 +610,7 @@
           "status":{"type":"string","description":"Status text."}
         }
       },
-      "TextprotoMIMEHeader":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},
-      "UuidUUID":{"type":"string","format":"uuid","example":"248df4b7-aa70-47b8-a036-33ac447e668d"}
+      "TextprotoMIMEHeader":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}
     },
     "securitySchemes":{"User":{"type":"apiKey","name":"sessid","in":"cookie"}}
   }
diff --git a/openapi/collector_test.go b/openapi/collector_test.go
index 35c052a..6854dc1 100644
--- a/openapi/collector_test.go
+++ b/openapi/collector_test.go
@@ -147,7 +147,7 @@ func TestCollector_Collect_requestMapping(t *testing.T) {
 	require.NoError(t, collector.Collect(http.MethodPost, "/test/{in-path}", u, h))
 	require.NoError(t, collector.Collect(http.MethodPut, "/test/{in-path}", u, h))
 
-	assertjson.EqualMarshal(t, []byte(`{
+	assertjson.EqMarshal(t, `{
 	  "openapi":"3.0.3","info":{"title":"","version":""},
 	  "paths":{
 		"/test/{in-path}":{
@@ -223,7 +223,7 @@ func TestCollector_Collect_requestMapping(t *testing.T) {
 		  }
 		}
 	  }
-	}`), collector.Reflector().SpecEns())
+	}`, collector.Reflector().SpecEns())
 
 	val := validatorMock{
 		AddSchemaFunc: func(in rest.ParamIn, name string, schemaData []byte, required bool) error {
@@ -267,7 +267,7 @@ func TestCollector_Collect_CombineErrors(t *testing.T) {
 
 	require.NoError(t, collector.Collect(http.MethodPost, "/test", u, h))
 
-	assertjson.EqualMarshal(t, []byte(`{
+	assertjson.EqMarshal(t, `{
 	  "openapi":"3.0.3","info":{"title":"","version":""},
 	  "paths":{
 		"/test":{
@@ -321,7 +321,7 @@ func TestCollector_Collect_CombineErrors(t *testing.T) {
 		  }
 		}
 	  }
-	}`), collector.Reflector().SpecEns())
+	}`, collector.Reflector().SpecEns())
 }
 
 // Output that implements OutputWithHTTPStatus interface.
@@ -349,7 +349,7 @@ func TestCollector_Collect_multipleHttpStatuses(t *testing.T) {
 		ReqValidator: &jsonschema.Validator{},
 	}))
 
-	assertjson.EqualMarshal(t, []byte(`{
+	assertjson.EqMarshal(t, `{
 	  "openapi": "3.0.3",
 	  "info": {
 		"title": "",
@@ -397,5 +397,73 @@ func TestCollector_Collect_multipleHttpStatuses(t *testing.T) {
 		  }
 		}
 	  }
-	}`), c.Reflector().SpecEns())
+	}`, c.Reflector().SpecEns())
+}
+
+func TestCollector_Collect_queryObject(t *testing.T) {
+	c := openapi.Collector{}
+	u := usecase.IOInteractor{}
+
+	type jsonFilter struct {
+		Foo string `json:"foo"`
+	}
+
+	type deepObjectFilter struct {
+		Bar string `query:"bar"`
+	}
+
+	type inputQueryObject struct {
+		Query            map[int]float64  `query:"in_query" description:"Object value in query."`
+		JSONFilter       jsonFilter       `query:"json_filter" description:"JSON object value in query."`
+		DeepObjectFilter deepObjectFilter `query:"deep_object_filter" description:"Deep object value in query params."`
+	}
+
+	u.Input = new(inputQueryObject)
+
+	require.NoError(t, c.Collect(http.MethodGet, "/foo", u, rest.HandlerTrait{
+		ReqValidator: &jsonschema.Validator{},
+	}))
+
+	assertjson.EqMarshal(t, `{
+	  "openapi":"3.0.3","info":{"title":"","version":""},
+	  "paths":{
+		"/foo":{
+		  "get":{
+			"parameters":[
+			  {
+				"name":"in_query","in":"query",
+				"description":"Object value in query.","style":"deepObject",
+				"explode":true,
+				"schema":{
+				  "type":"object","additionalProperties":{"type":"number"},
+				  "description":"Object value in query."
+				}
+			  },
+			  {
+				"name":"json_filter","in":"query",
+				"description":"JSON object value in query.",
+				"content":{
+				  "application/json":{
+					"schema":{"$ref":"#/components/schemas/QueryOpenapiTestJsonFilter"}
+				  }
+				}
+			  },
+			  {
+				"name":"deep_object_filter","in":"query",
+				"description":"Deep object value in query params.",
+				"style":"deepObject","explode":true,
+				"schema":{"$ref":"#/components/schemas/QueryOpenapiTestDeepObjectFilter"}
+			  }
+			],
+			"responses":{"204":{"description":"No Content"}}
+		  }
+		}
+	  },
+	  "components":{
+		"schemas":{
+		  "QueryOpenapiTestDeepObjectFilter":{"type":"object","properties":{"bar":{"type":"string"}}},
+		  "QueryOpenapiTestJsonFilter":{"type":"object","properties":{"foo":{"type":"string"}}}
+		}
+	  }
+	}`, c.Reflector().SpecEns())
 }
diff --git a/request/factory_test.go b/request/factory_test.go
index 3f3cdfd..6572e45 100644
--- a/request/factory_test.go
+++ b/request/factory_test.go
@@ -117,10 +117,25 @@ func BenchmarkDecoderFactory_SetDecoderFunc(b *testing.B) {
 }
 
 func TestDecoderFactory_MakeDecoder_default(t *testing.T) {
+	type Embed struct {
+		Baz bool `query:"baz" default:"true"`
+	}
+
+	type DeeplyEmbedded struct {
+		Embed
+	}
+
 	type MyInput struct {
-		ID         int    `query:"id" default:"123"`
-		Name       string `header:"X-Name" default:"foo"`
-		unexported bool   `query:"unexported"` // This field is skipped because it is unexported.
+		ID     int    `query:"id" default:"123"`
+		Name   string `header:"X-Name" default:"foo"`
+		Deeper struct {
+			Foo        string `query:"foo" default:"abc"`
+			EvenDeeper struct {
+				Bar float64 `query:"bar" default:"1.23"`
+			} `query:"even_deeper"`
+		} `query:"deeper"`
+		*DeeplyEmbedded
+		unexported bool `query:"unexported"` // This field is skipped because it is unexported.
 	}
 
 	df := request.NewDecoderFactory()
@@ -138,8 +153,15 @@ func TestDecoderFactory_MakeDecoder_default(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "foo", i.Name)
 	assert.Equal(t, 123, i.ID)
-
-	req, err = http.NewRequest(http.MethodPost, "/?id=321", nil)
+	assert.Equal(t, "abc", i.Deeper.Foo)
+	assert.Equal(t, 1.23, i.Deeper.EvenDeeper.Bar)
+	assert.Equal(t, true, i.Baz)
+
+	req, err = http.NewRequest(
+		http.MethodPost,
+		"/?id=321&deeper[foo]=def&deeper[even_deeper][bar]=3.21&baz=false",
+		nil,
+	)
 	require.NoError(t, err)
 
 	req.Header.Set("X-Name", "bar")
@@ -150,6 +172,9 @@ func TestDecoderFactory_MakeDecoder_default(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "bar", i.Name)
 	assert.Equal(t, 321, i.ID)
+	assert.Equal(t, "def", i.Deeper.Foo)
+	assert.Equal(t, 3.21, i.Deeper.EvenDeeper.Bar)
+	assert.Equal(t, false, i.Baz)
 }
 
 func TestDecoderFactory_MakeDecoder_invalidMapping(t *testing.T) {