Repository: swaggest/rest
Pull request #8 (2020-11-24 10:26:48)

Allow JSON values in scalar parameters

codecov[bot]: # [Codecov](https://codecov.io/gh/swaggest/rest/pull/8?src=pr&el=h1) Report
> Merging [#8](https://codecov.io/gh/swaggest/rest/pull/8?src=pr&el=desc) (3d1b7a9) into [master](https://codecov.io/gh/swaggest/rest/commit/0136be27420f5f665992da09fbd22df346aa25ca?el=desc) (0136be2) will **decrease** coverage by `0.23%`.
> The diff coverage is `56.25%`.

[![Impacted file tree graph](https://codecov.io/gh/swaggest/rest/pull/8/graphs/tree.svg?width=650&height=150&src=pr&token=OpLHxxPdmn)](https://codecov.io/gh/swaggest/rest/pull/8?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master       #8      +/-   ##
==========================================
- Coverage   69.03%   68.80%   -0.24%     
==========================================
  Files          25       25              
  Lines        1321     1343      +22     
==========================================
+ Hits          912      924      +12     
- Misses        310      316       +6     
- Partials       99      103       +4     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `68.80% <56.25%> (-0.24%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/swaggest/rest/pull/8?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [openapi/collector.go](https://codecov.io/gh/swaggest/rest/pull/8/diff?src=pr&el=tree#diff-b3BlbmFwaS9jb2xsZWN0b3IuZ28=) | `41.34% <0.00%> (ø)` | |
| [jsonschema/validator.go](https://codecov.io/gh/swaggest/rest/pull/8/diff?src=pr&el=tree#diff-anNvbnNjaGVtYS92YWxpZGF0b3IuZ28=) | `58.94% <50.00%> (-0.61%)` | :arrow_down: |
| [request/factory.go](https://codecov.io/gh/swaggest/rest/pull/8/diff?src=pr&el=tree#diff-cmVxdWVzdC9mYWN0b3J5Lmdv) | `82.14% <61.53%> (-3.78%)` | :arrow_down: |
| [request/decoder.go](https://codecov.io/gh/swaggest/rest/pull/8/diff?src=pr&el=tree#diff-cmVxdWVzdC9kZWNvZGVyLmdv) | `73.68% <100.00%> (-3.46%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/swaggest/rest/pull/8?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/swaggest/rest/pull/8?src=pr&el=footer). Last update [0136be2...3d1b7a9](https://codecov.io/gh/swaggest/rest/pull/8?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index cb79f56..c42e65d 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -14,10 +14,10 @@ jobs:
     steps:
       - uses: actions/checkout@v2
       - name: golangci-lint
-        uses: golangci/golangci-lint-action@v2.2.1
+        uses: golangci/golangci-lint-action@v2.3.0
         with:
           # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
-          version: v1.31
+          version: v1.32.2
 
           # Optional: golangci-lint command line arguments.
           # args: ./the-only-dir-to-analyze/...
diff --git a/.golangci.yml b/.golangci.yml
index 837e39c..7d9b294 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -24,6 +24,8 @@ linters:
     - gochecknoglobals
     - testpackage
     - goerr113
+    - wrapcheck
+    - exhaustivestruct
 
 issues:
   exclude-use-default: false
diff --git a/Makefile b/Makefile
index 02b55a5..28802ce 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-GOLANGCI_LINT_VERSION := "v1.31.0"
+GOLANGCI_LINT_VERSION := "v1.32.2"
 BENCH_COUNT ?= 5
 REF_NAME ?= $(shell git symbolic-ref HEAD --short | tr / - 2>/dev/null)
 
diff --git a/_examples/Makefile b/_examples/Makefile
index d5484f9..018b6f3 100644
--- a/_examples/Makefile
+++ b/_examples/Makefile
@@ -1,4 +1,4 @@
-GOLANGCI_LINT_VERSION := "v1.31.0"
+GOLANGCI_LINT_VERSION := "v1.32.2"
 
 # The head of Makefile determines location of dev-go to include standard targets.
 GO ?= go
diff --git a/_examples/advanced/json_param.go b/_examples/advanced/json_param.go
new file mode 100644
index 0000000..34be56b
--- /dev/null
+++ b/_examples/advanced/json_param.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+	"context"
+
+	"github.com/swaggest/usecase"
+)
+
+func jsonParam() usecase.Interactor {
+	u := struct {
+		usecase.Interactor
+		usecase.Info
+		usecase.WithInput
+		usecase.WithOutput
+	}{}
+
+	u.SetTitle("Request With JSON Query Parameter")
+	u.SetDescription("Request with JSON body and query/header/path params, response with JSON body and data from request.")
+
+	type JSONPayload struct {
+		ID   int    `json:"id"`
+		Name string `json:"name"`
+	}
+
+	type inputWithJSON struct {
+		Header   string      `header:"X-Header" description:"Simple scalar value in header."`
+		Query    int         `query:"in_query" description:"Simple scalar value in query."`
+		Path     string      `path:"in-path" description:"Simple scalar value in path"`
+		Identity JSONPayload `query:"identity" description:"JSON value in query"`
+	}
+
+	type outputWithJSON struct {
+		Header string `json:"inHeader"`
+		Query  int    `json:"inQuery"`
+		Path   string `json:"inPath"`
+		JSONPayload
+	}
+
+	u.Input = new(inputWithJSON)
+	u.Output = new(outputWithJSON)
+
+	u.Interactor = usecase.Interact(func(ctx context.Context, input, output interface{}) (err error) {
+		var (
+			in  = input.(*inputWithJSON)
+			out = output.(*outputWithJSON)
+		)
+
+		out.Query = in.Query
+		out.Header = in.Header
+		out.Path = in.Path
+		out.JSONPayload = in.Identity
+
+		return nil
+	})
+
+	return u
+}
diff --git a/_examples/advanced/router.go b/_examples/advanced/router.go
index cdad4fb..953827c 100644
--- a/_examples/advanced/router.go
+++ b/_examples/advanced/router.go
@@ -57,6 +57,7 @@ func NewRouter() http.Handler {
 
 	r.Method(http.MethodPost, "/file-upload", nethttp.NewHandler(fileUploader()))
 	r.Method(http.MethodPost, "/file-multi-upload", nethttp.NewHandler(fileMultiUploader()))
+	r.Method(http.MethodGet, "/json-param/{in-path}", nethttp.NewHandler(jsonParam()))
 	r.Method(http.MethodPost, "/json-body/{in-path}", nethttp.NewHandler(jsonBody()))
 	r.Method(http.MethodPost, "/json-body-validation/{in-path}", nethttp.NewHandler(jsonBodyValidation()))
 	r.Method(http.MethodPost, "/json-slice-body", nethttp.NewHandler(jsonSliceBody()))
diff --git a/_examples/go.mod b/_examples/go.mod
index 4b841e9..5443670 100644
--- a/_examples/go.mod
+++ b/_examples/go.mod
@@ -12,10 +12,10 @@ require (
 	github.com/kelseyhightower/envconfig v1.4.0
 	github.com/klauspost/compress v1.11.1 // indirect
 	github.com/stretchr/testify v1.6.1
-	github.com/swaggest/assertjson v1.4.0
-	github.com/swaggest/jsonschema-go v0.3.12
-	github.com/swaggest/openapi-go v0.2.4
-	github.com/swaggest/rest v0.0.0-20201021182948-25d31f7f38d4
+	github.com/swaggest/assertjson v1.4.1
+	github.com/swaggest/jsonschema-go v0.3.13
+	github.com/swaggest/openapi-go v0.2.6
+	github.com/swaggest/rest v0.1.3
 	github.com/swaggest/swgui v1.0.9
 	github.com/swaggest/usecase v0.0.0-20200928062416-27f47131b0f8
 	github.com/valyala/fasthttp v1.16.0
diff --git a/_examples/go.sum b/_examples/go.sum
index 3b0a44f..fabfea8 100644
--- a/_examples/go.sum
+++ b/_examples/go.sum
@@ -2,11 +2,7 @@ github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu
 github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
 github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
 github.com/bool64/dev v0.1.0/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
-github.com/bool64/dev v0.1.2/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/bool64/dev v0.1.3/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
-github.com/bool64/dev v0.1.5/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
-github.com/bool64/dev v0.1.6 h1:QP8buknknKrPVizPbOKeyCt6/2+iZ309wxjreckowVY=
-github.com/bool64/dev v0.1.6/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/bool64/dev v0.1.7 h1:1xs4ff6lUFIt7euQCmfjU0Xi7IiSmwcQyMokKdq6qAM=
 github.com/bool64/dev v0.1.7/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/bool64/httptestbench v0.1.0 h1:Nr+9KjZbrlgmd8ABGfFvEYEnMdbAWxj13YHeQR6OOzA=
@@ -27,7 +23,6 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg=
 github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
@@ -74,32 +69,21 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/swaggest/assertjson v1.0.0/go.mod h1:mE5ltBbrB+Ya8Xar5OMITxya76vwLZMoPG8aXF7sVQc=
-github.com/swaggest/assertjson v1.2.0/go.mod h1:VR1/jzZHtROewu/SBEdFrBOBuNm1FO9LDjvP9fLf6+I=
-github.com/swaggest/assertjson v1.3.0 h1:751J+Plmg68cDGeMwMIq5nEvBw8g+ErG4UMcv9kpHmk=
-github.com/swaggest/assertjson v1.3.0/go.mod h1:VR1/jzZHtROewu/SBEdFrBOBuNm1FO9LDjvP9fLf6+I=
-github.com/swaggest/assertjson v1.4.0 h1:WPHYWa102jB8BnO76+86IquNH5e3wllC60TsUT4Iq4U=
-github.com/swaggest/assertjson v1.4.0/go.mod h1:mJG4OUkBTu+H4jT2oSQ1Y1DrRSu81PtSJ6bTR6e8avg=
+github.com/swaggest/assertjson v1.4.1 h1:IpRjS42lEXKHR8C409jGCNDFCm3KCvpTi9LSksa490k=
+github.com/swaggest/assertjson v1.4.1/go.mod h1:v7atNMMlIa5ry2H1rgCHhvz7eho8yfP85UGio0+TFEI=
 github.com/swaggest/form v3.6.4+incompatible h1:K0EaZjf8C7cScuZgnJG+Sg0GUGAtjPZivTv1jN+1ni4=
 github.com/swaggest/form v3.6.4+incompatible/go.mod h1:eTJPpSayjnJlOQQbDYsSHlbREbSAtudNW/Y6HouVWr0=
 github.com/swaggest/jsonschema-go v0.2.1/go.mod h1:QFauBdPTrU1UltwocM5FzOWnVjVVtcWkJWG3NlK9sV0=
 github.com/swaggest/jsonschema-go v0.2.4/go.mod h1:m4VV88Gbi7lCrt9ckJzigK1rMlEeFjdZUkJr1o5MnDE=
-github.com/swaggest/jsonschema-go v0.3.8/go.mod h1:AQijowS82ZcUId9RStqvyGTxyhzYUiVDxelwQxEdOy8=
-github.com/swaggest/jsonschema-go v0.3.11 h1:rbYBJhAV2Uz1PB4iJyKbvzGO+o1FsdX60aLi+o67IHU=
-github.com/swaggest/jsonschema-go v0.3.11/go.mod h1:lJUbAc2E3OpGrrUGvayONeRBXsFJeedWTpm23jhOtKs=
-github.com/swaggest/jsonschema-go v0.3.12 h1:KgzIVv3OYCcwjUBdkYLYcQXQ/0q+40cSwZBtHDF+gKY=
-github.com/swaggest/jsonschema-go v0.3.12/go.mod h1:UXDViNdMa0fEpc/QgPu9ac1itOuxgbZyExZI6pg024A=
+github.com/swaggest/jsonschema-go v0.3.13 h1:QKgsj3PrrNZBs68JZ6TJZTFT943AO48oGIsVwWhO47s=
+github.com/swaggest/jsonschema-go v0.3.13/go.mod h1:INO3Dt0DeyNCbQbqMXNpMQ489BcyRtSb/k62OfmHbpY=
 github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM=
-github.com/swaggest/openapi-go v0.2.2 h1:ZV6tjpR/W97sIOknkaOAt/U2FpqoEptRZrYiksfNKkY=
-github.com/swaggest/openapi-go v0.2.2/go.mod h1:GyvI08XD7gnTZxFteI2/Rnk6hgmcSufqG8wVBz/yzqo=
-github.com/swaggest/openapi-go v0.2.3 h1:DsbY56dzJQzKLjoJKl15WtykoyF1OJrqIzUUH+ovTDg=
-github.com/swaggest/openapi-go v0.2.3/go.mod h1:R4cj4xlxiODL8m+x0pKbWXRZYx67UTYOx0xfH0zxm3k=
-github.com/swaggest/openapi-go v0.2.4 h1:7OkZxIJ6PGH9n4ULguE87utNxcEQqa4/2KEmCmCf/rI=
-github.com/swaggest/openapi-go v0.2.4/go.mod h1:+PRHGYsaroCoINZDx/m+fxlKaRp+Mwu6QlG4VbloKtI=
+github.com/swaggest/openapi-go v0.2.6 h1:fggork4s9h+tFNn6M2ciMqYrpH5upmEOwEHZdg2Y3PY=
+github.com/swaggest/openapi-go v0.2.6/go.mod h1:vJDQ4PLJhrUdNvyuGfQnJOOR+KuCYPCyOjt/3h/yHj8=
 github.com/swaggest/refl v0.1.0/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o=
 github.com/swaggest/refl v0.1.2/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o=
-github.com/swaggest/refl v0.1.3/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o=
-github.com/swaggest/refl v0.1.5 h1:dsIhZmcVkNqsnnc4SZO7ezp7j8cTn+N/i8ND+0WxA/I=
-github.com/swaggest/refl v0.1.5/go.mod h1:qeng4aRKeRt8NYAuLpag+FY9A/pDMkSwe7zhXn6/LW4=
+github.com/swaggest/refl v0.1.6 h1:z5drruoOSfWoB4aPuL22cMB5suLuxXS22oQkpTQ093o=
+github.com/swaggest/refl v0.1.6/go.mod h1:XqZZZ8i6hptbvd3A8pvXkF0ontg0Wziqe7OFAnvAZMk=
 github.com/swaggest/swgen v0.6.20/go.mod h1:ipkZNfwztgRfbOWUllLZawfmxXprT8flqNJO9XhoMwM=
 github.com/swaggest/swgen v0.6.23 h1:9COrDC1o34xJS0NeQ2js7qGfo/Rgs16xWT0pQRvGNyY=
 github.com/swaggest/swgen v0.6.23/go.mod h1:gj2yCLONy3kosKjwRtQeT5O9qqlhUvXAiDnbVwBUNFM=
diff --git a/go.mod b/go.mod
index 5b7d592..635a692 100644
--- a/go.mod
+++ b/go.mod
@@ -8,10 +8,10 @@ require (
 	github.com/go-chi/chi v4.1.2+incompatible
 	github.com/santhosh-tekuri/jsonschema/v2 v2.2.0
 	github.com/stretchr/testify v1.6.1
-	github.com/swaggest/assertjson v1.4.0
+	github.com/swaggest/assertjson v1.4.1
 	github.com/swaggest/form v3.6.4+incompatible
-	github.com/swaggest/openapi-go v0.2.4
-	github.com/swaggest/refl v0.1.5
+	github.com/swaggest/openapi-go v0.2.6
+	github.com/swaggest/refl v0.1.6
 	github.com/swaggest/usecase v0.0.0-20200928062416-27f47131b0f8
 	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
 	golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
diff --git a/go.sum b/go.sum
index 69d8821..37524b0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,4 @@
 github.com/bool64/dev v0.1.0/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
-github.com/bool64/dev v0.1.2/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
-github.com/bool64/dev v0.1.6 h1:QP8buknknKrPVizPbOKeyCt6/2+iZ309wxjreckowVY=
-github.com/bool64/dev v0.1.6/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/bool64/dev v0.1.7 h1:1xs4ff6lUFIt7euQCmfjU0Xi7IiSmwcQyMokKdq6qAM=
 github.com/bool64/dev v0.1.7/go.mod h1:pn52JC52uSgpazChx9CeXyG+S3sW2V36HHoLNBbscdg=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
@@ -20,7 +17,6 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/iancoleman/orderedmap v0.1.0 h1:2orAxZBJsvimgEBmMWfXaFlzSG2fbQil5qzP3F6cCkg=
 github.com/iancoleman/orderedmap v0.1.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
@@ -59,23 +55,21 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/swaggest/assertjson v1.0.0/go.mod h1:mE5ltBbrB+Ya8Xar5OMITxya76vwLZMoPG8aXF7sVQc=
-github.com/swaggest/assertjson v1.2.0/go.mod h1:VR1/jzZHtROewu/SBEdFrBOBuNm1FO9LDjvP9fLf6+I=
-github.com/swaggest/assertjson v1.4.0 h1:WPHYWa102jB8BnO76+86IquNH5e3wllC60TsUT4Iq4U=
-github.com/swaggest/assertjson v1.4.0/go.mod h1:mJG4OUkBTu+H4jT2oSQ1Y1DrRSu81PtSJ6bTR6e8avg=
+github.com/swaggest/assertjson v1.4.1 h1:IpRjS42lEXKHR8C409jGCNDFCm3KCvpTi9LSksa490k=
+github.com/swaggest/assertjson v1.4.1/go.mod h1:v7atNMMlIa5ry2H1rgCHhvz7eho8yfP85UGio0+TFEI=
 github.com/swaggest/form v3.6.4+incompatible h1:K0EaZjf8C7cScuZgnJG+Sg0GUGAtjPZivTv1jN+1ni4=
 github.com/swaggest/form v3.6.4+incompatible/go.mod h1:eTJPpSayjnJlOQQbDYsSHlbREbSAtudNW/Y6HouVWr0=
 github.com/swaggest/jsonschema-go v0.2.1/go.mod h1:QFauBdPTrU1UltwocM5FzOWnVjVVtcWkJWG3NlK9sV0=
 github.com/swaggest/jsonschema-go v0.2.4/go.mod h1:m4VV88Gbi7lCrt9ckJzigK1rMlEeFjdZUkJr1o5MnDE=
-github.com/swaggest/jsonschema-go v0.3.12 h1:KgzIVv3OYCcwjUBdkYLYcQXQ/0q+40cSwZBtHDF+gKY=
-github.com/swaggest/jsonschema-go v0.3.12/go.mod h1:UXDViNdMa0fEpc/QgPu9ac1itOuxgbZyExZI6pg024A=
+github.com/swaggest/jsonschema-go v0.3.13 h1:QKgsj3PrrNZBs68JZ6TJZTFT943AO48oGIsVwWhO47s=
+github.com/swaggest/jsonschema-go v0.3.13/go.mod h1:INO3Dt0DeyNCbQbqMXNpMQ489BcyRtSb/k62OfmHbpY=
 github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM=
-github.com/swaggest/openapi-go v0.2.4 h1:7OkZxIJ6PGH9n4ULguE87utNxcEQqa4/2KEmCmCf/rI=
-github.com/swaggest/openapi-go v0.2.4/go.mod h1:+PRHGYsaroCoINZDx/m+fxlKaRp+Mwu6QlG4VbloKtI=
+github.com/swaggest/openapi-go v0.2.6 h1:fggork4s9h+tFNn6M2ciMqYrpH5upmEOwEHZdg2Y3PY=
+github.com/swaggest/openapi-go v0.2.6/go.mod h1:vJDQ4PLJhrUdNvyuGfQnJOOR+KuCYPCyOjt/3h/yHj8=
 github.com/swaggest/refl v0.1.0/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o=
 github.com/swaggest/refl v0.1.2/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o=
-github.com/swaggest/refl v0.1.3/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o=
-github.com/swaggest/refl v0.1.5 h1:dsIhZmcVkNqsnnc4SZO7ezp7j8cTn+N/i8ND+0WxA/I=
-github.com/swaggest/refl v0.1.5/go.mod h1:qeng4aRKeRt8NYAuLpag+FY9A/pDMkSwe7zhXn6/LW4=
+github.com/swaggest/refl v0.1.6 h1:z5drruoOSfWoB4aPuL22cMB5suLuxXS22oQkpTQ093o=
+github.com/swaggest/refl v0.1.6/go.mod h1:XqZZZ8i6hptbvd3A8pvXkF0ontg0Wziqe7OFAnvAZMk=
 github.com/swaggest/swgen v0.6.20/go.mod h1:ipkZNfwztgRfbOWUllLZawfmxXprT8flqNJO9XhoMwM=
 github.com/swaggest/swgen v0.6.23 h1:9COrDC1o34xJS0NeQ2js7qGfo/Rgs16xWT0pQRvGNyY=
 github.com/swaggest/swgen v0.6.23/go.mod h1:gj2yCLONy3kosKjwRtQeT5O9qqlhUvXAiDnbVwBUNFM=
diff --git a/jsonschema/validator.go b/jsonschema/validator.go
index 43ab9a5..fac3fcf 100644
--- a/jsonschema/validator.go
+++ b/jsonschema/validator.go
@@ -158,6 +158,7 @@ func (v *Validator) ValidateJSONBody(jsonBody []byte) error {
 
 	errs := make(rest.ValidationErrors, 1)
 
+	// nolint:errorlint // Error is not wrapped, type assertion is more performant.
 	if ve, ok := err.(*jsonschema.ValidationError); ok {
 		errs[name] = appendError(errs[name], ve)
 	} else {
@@ -207,6 +208,7 @@ func (v *Validator) ValidateData(in rest.ParamIn, namedData map[string]interface
 
 		errKey := string(in) + ":" + name
 
+		// nolint:errorlint // Error is not wrapped, type assertion is more performant.
 		if ve, ok := err.(*jsonschema.ValidationError); ok {
 			errs[errKey] = appendError(errs[errKey], ve)
 		} else {
diff --git a/openapi/collector.go b/openapi/collector.go
index 4627cc7..d3dc6e7 100644
--- a/openapi/collector.go
+++ b/openapi/collector.go
@@ -254,7 +254,7 @@ func (c *Collector) provideParametersJSONSchemas(op openapi3.Operation, validato
 
 		err = validator.AddSchema(rest.ParamIn(pp.In), pp.Name, schemaData, required)
 		if err != nil {
-			return fmt.Errorf("failed to add validation schema for parameter (%s, %s): %v", pp.In, pp.Name, err)
+			return fmt.Errorf("failed to add validation schema for parameter (%s, %s): %w", pp.In, pp.Name, err)
 		}
 	}
 
@@ -312,7 +312,7 @@ func (c *Collector) ProvideRequestJSONSchemas(
 
 			err = validator.AddSchema(in, "body", schemaData, false)
 			if err != nil {
-				return fmt.Errorf("failed to add validation schema for request body: %v", err)
+				return fmt.Errorf("failed to add validation schema for request body: %w", err)
 			}
 		}
 	}
@@ -348,7 +348,7 @@ func (c *Collector) provideHeaderSchemas(resp *openapi3.Response, validator rest
 
 		err = validator.AddSchema(rest.ParamInHeader, name, schemaData, required)
 		if err != nil {
-			return fmt.Errorf("failed to add validation schema for response header (%s): %v", name, err)
+			return fmt.Errorf("failed to add validation schema for response header (%s): %w", name, err)
 		}
 	}
 
@@ -399,7 +399,7 @@ func (c *Collector) ProvideResponseJSONSchemas(
 		}
 
 		if err := validator.AddSchema(rest.ParamInBody, "body", schemaData, false); err != nil {
-			return fmt.Errorf("failed to add validation schema for response body: %v", err)
+			return fmt.Errorf("failed to add validation schema for response body: %w", err)
 		}
 	}
 
diff --git a/request/decoder.go b/request/decoder.go
index 78783b4..685fbbb 100644
--- a/request/decoder.go
+++ b/request/decoder.go
@@ -54,6 +54,7 @@ func (d *decoder) Decode(r *http.Request, input interface{}, validator rest.Vali
 	for i, decode := range d.decoders {
 		err := decode(r, input, validator)
 		if err != nil {
+			// nolint:errorlint // Error is not wrapped, type assertion is more performant.
 			if de, ok := err.(form.DecodeErrors); ok {
 				errs := make(rest.RequestErrors, len(de))
 				for name, e := range de {
diff --git a/request/factory.go b/request/factory.go
index 3c6cc0c..faaaa88 100644
--- a/request/factory.go
+++ b/request/factory.go
@@ -1,6 +1,8 @@
 package request
 
 import (
+	"bytes"
+	"encoding/json"
 	"io"
 	"net/http"
 	"net/url"
@@ -109,7 +111,7 @@ func (df *DecoderFactory) MakeDecoder(
 		}
 
 		if refl.HasTaggedFields(input, string(in)) {
-			// dfu :=  df.decoderFunctions[in]
+			df.jsonParams(formDecoder, in, input)
 			m.decoders = append(m.decoders, makeDecoder(in, formDecoder, df.decoderFunctions[in]))
 			m.in = append(m.in, in)
 		}
@@ -142,6 +144,34 @@ func (df *DecoderFactory) MakeDecoder(
 	return &m
 }
 
+// jsonParams configures custom decoding for parameters with JSON struct values.
+func (df *DecoderFactory) jsonParams(formDecoder *form.Decoder, in rest.ParamIn, input interface{}) {
+	// Check fields for struct values with json tags. E.g. query parameter with json value.
+	refl.WalkTaggedFields(reflect.ValueOf(input), func(v reflect.Value, sf reflect.StructField, tag string) {
+		fieldVal := v.Interface()
+
+		if refl.HasTaggedFields(fieldVal, jsonTag) {
+			// 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) {
+				var err error
+				f := reflect.New(sf.Type)
+				if df.JSONReader != nil {
+					err = df.JSONReader(bytes.NewBufferString(s), f.Interface())
+				} else {
+					err = json.Unmarshal([]byte(s), f.Interface())
+				}
+
+				if err != nil {
+					return nil, err
+				}
+
+				return reflect.Indirect(f).Interface(), nil
+			}, fieldVal)
+		}
+	}, string(in))
+}
+
 func (df *DecoderFactory) makeDefaultDecoder(input interface{}, m *decoder) {
 	defaults := url.Values{}
 


Test patch:
diff --git a/_examples/advanced/_testdata/openapi.json b/_examples/advanced/_testdata/openapi.json
index 93ff9f2..31819a1 100644
--- a/_examples/advanced/_testdata/openapi.json
+++ b/_examples/advanced/_testdata/openapi.json
@@ -1,5 +1,5 @@
 {
-  "openapi":"3.0.2",
+  "openapi":"3.0.3",
   "info":{"title":"Advanced Example","description":"This app showcases a variety of features.","version":"v1.2.3"},
   "paths":{
     "/file-multi-upload":{
@@ -92,11 +92,11 @@
             "schema":{"minLength":3,"type":"string","description":"Simple scalar value in header."}
           }
         ],
-        "requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedInputWithJSONType2"}}}},
+        "requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedInputWithJSONType3"}}}},
         "responses":{
           "200":{
             "description":"OK",
-            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputWithJSONType2"}}}
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputWithJSONType3"}}}
           }
         }
       }
@@ -120,11 +120,11 @@
             "schema":{"type":"string","description":"Simple scalar value in header."}
           }
         ],
-        "requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedInputWithJSON"}}}},
+        "requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedInputWithJSONType2"}}}},
         "responses":{
           "200":{
             "description":"OK",
-            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputWithJSON"}}}
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputWithJSONType2"}}}
           }
         }
       }
@@ -151,6 +151,38 @@
         }
       }
     },
+    "/json-param/{in-path}":{
+      "get":{
+        "summary":"Request With JSON Query Parameter",
+        "description":"Request with JSON body and query/header/path params, response with JSON body and data from request.",
+        "operationId":"",
+        "parameters":[
+          {
+            "name":"in_query","in":"query","description":"Simple scalar value in query.",
+            "schema":{"type":"integer","description":"Simple scalar value in query."}
+          },
+          {
+            "name":"identity","in":"query","description":"JSON value in query",
+            "schema":{"$ref":"#/components/schemas/AdvancedJSONPayload"},
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedJSONPayload"}}}
+          },
+          {
+            "name":"in-path","in":"path","description":"Simple scalar value in path","required":true,
+            "schema":{"type":"string","description":"Simple scalar value in path"}
+          },
+          {
+            "name":"X-Header","in":"header","description":"Simple scalar value in header.",
+            "schema":{"type":"string","description":"Simple scalar value in header."}
+          }
+        ],
+        "responses":{
+          "200":{
+            "description":"OK",
+            "content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdvancedOutputWithJSON"}}}
+          }
+        }
+      }
+    },
     "/json-slice-body":{
       "post":{
         "summary":"Request With JSON Array In Body","description":"","operationId":"",
@@ -336,9 +368,10 @@
           }
         }
       },
-      "AdvancedInputWithJSON":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}},
-      "AdvancedInputWithJSONType2":{"type":"object","properties":{"id":{"minimum":100,"type":"integer"},"name":{"minLength":3,"type":"string"}}},
+      "AdvancedInputWithJSONType2":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}},
+      "AdvancedInputWithJSONType3":{"type":"object","properties":{"id":{"minimum":100,"type":"integer"},"name":{"minLength":3,"type":"string"}}},
       "AdvancedJSONMapPayload":{"type":"object","additionalProperties":{"type":"number"},"nullable":true},
+      "AdvancedJSONPayload":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}},
       "AdvancedJSONSlicePayload":{"type":"array","items":{"type":"integer"},"nullable":true},
       "AdvancedJsonMapReq":{"type":"object","additionalProperties":{"type":"number"},"nullable":true},
       "AdvancedJsonOutput":{
@@ -372,6 +405,13 @@
         }
       },
       "AdvancedOutputWithJSONType2":{
+        "type":"object",
+        "properties":{
+          "id":{"type":"integer"},"inHeader":{"type":"string"},"inPath":{"type":"string"},"inQuery":{"type":"integer"},
+          "name":{"type":"string"}
+        }
+      },
+      "AdvancedOutputWithJSONType3":{
         "type":"object",
         "properties":{
           "id":{"minimum":100,"type":"integer"},"inHeader":{"minLength":3,"type":"string"},
@@ -413,7 +453,7 @@
           "status":{"type":"string","description":"Status text."}
         }
       },
-      "TextprotoMIMEHeader":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}
+      "TextprotoMIMEHeader":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}},"nullable":true}
     }
   }
-}
+}
\ No newline at end of file
diff --git a/_examples/advanced/gzip_pass_through_test.go b/_examples/advanced/gzip_pass_through_test.go
index 23d6d12..a8fbc87 100644
--- a/_examples/advanced/gzip_pass_through_test.go
+++ b/_examples/advanced/gzip_pass_through_test.go
@@ -51,7 +51,7 @@ func Test_directGzip_perf(t *testing.T) {
 	if httptestbench.RaceDetectorEnabled {
 		assert.Less(t, res.Extra["B:rcvd/op"], 640.0)
 		assert.Less(t, res.Extra["B:sent/op"], 104.0)
-		assert.Less(t, res.AllocsPerOp(), int64(55))
+		assert.Less(t, res.AllocsPerOp(), int64(56))
 		assert.Less(t, res.AllocedBytesPerOp(), int64(8000))
 	} else {
 		assert.Less(t, res.Extra["B:rcvd/op"], 625.0)
diff --git a/nethttp/openapi_test.go b/nethttp/openapi_test.go
index 326fc19..3ffbca3 100644
--- a/nethttp/openapi_test.go
+++ b/nethttp/openapi_test.go
@@ -62,7 +62,7 @@ func TestOpenAPIMiddleware(t *testing.T) {
 	require.NoError(t, err)
 
 	assertjson.Equal(t, []byte(`{
-	 "openapi":"3.0.2","info":{"title":"","version":""},
+	 "openapi":"3.0.3","info":{"title":"","version":""},
 	 "paths":{
 	  "/test":{
 	   "get":{
diff --git a/openapi/collector_test.go b/openapi/collector_test.go
index 59f3d72..c60f68d 100644
--- a/openapi/collector_test.go
+++ b/openapi/collector_test.go
@@ -115,7 +115,7 @@ func TestCollector_Collect_requestMapping(t *testing.T) {
 	require.NoError(t, err)
 
 	assertjson.Equal(t, []byte(`{
-	  "openapi":"3.0.2","info":{"title":"","version":""},
+	  "openapi":"3.0.3","info":{"title":"","version":""},
 	  "paths":{
 		"/test/{in-path}":{
 		  "post":{
diff --git a/request/decoder_test.go b/request/decoder_test.go
index dab825f..d4cfb8d 100644
--- a/request/decoder_test.go
+++ b/request/decoder_test.go
@@ -283,3 +283,54 @@ func BenchmarkDecoder_Decode_queryObject(b *testing.B) {
 		}
 	}
 }
+
+func TestDecoder_Decode_jsonParam(t *testing.T) {
+	type inp struct {
+		Filter struct {
+			A int    `json:"a"`
+			B string `json:"b"`
+		} `query:"filter"`
+	}
+
+	df := request.NewDecoderFactory()
+	dec := df.MakeDecoder(http.MethodGet, new(inp), nil)
+
+	req, err := http.NewRequest(http.MethodGet, "/?filter=%7B%22a%22%3A123%2C%22b%22%3A%22abc%22%7D", nil)
+	require.NoError(t, err)
+
+	v := new(inp)
+	require.NoError(t, dec.Decode(req, v, nil))
+
+	assert.Equal(t, 123, v.Filter.A)
+	assert.Equal(t, "abc", v.Filter.B)
+}
+
+// BenchmarkDecoder_Decode_jsonParam-4   	  525867	      2306 ns/op	     752 B/op	      12 allocs/op.
+func BenchmarkDecoder_Decode_jsonParam(b *testing.B) {
+	type inp struct {
+		Filter struct {
+			A int    `json:"a"`
+			B string `json:"b"`
+		} `query:"filter"`
+	}
+
+	df := request.NewDecoderFactory()
+	dec := df.MakeDecoder(http.MethodGet, new(inp), nil)
+
+	req, err := http.NewRequest(http.MethodGet, "/?filter=%7B%22a%22%3A123%2C%22b%22%3A%22abc%22%7D", nil)
+	require.NoError(b, err)
+
+	v := new(inp)
+
+	b.ReportAllocs()
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		err := dec.Decode(req, v, nil)
+		if err != nil {
+			b.Fail()
+		}
+	}
+	assert.Equal(b, 123, v.Filter.A)
+	assert.Equal(b, "abc", v.Filter.B)
+}