> $GITHUB_OUTPUT && echo "$OUTPUT" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT
36 | - name: Comment report
37 | continue-on-error: true
38 | uses: marocchino/sticky-pull-request-comment@v2
39 | with:
40 | header: gorelease
41 | message: |
42 | ### Go API Changes
43 |
44 |
45 | ${{ steps.gorelease.outputs.report }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/test-unit.yml:
--------------------------------------------------------------------------------
1 | # This script is provided by github.com/bool64/dev.
2 | name: test-unit
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | pull_request:
9 |
10 | # Cancel the workflow in progress in newer build is about to start.
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
13 | cancel-in-progress: true
14 |
15 | env:
16 | GO111MODULE: "on"
17 | RUN_BASE_COVERAGE: "on" # Runs test for PR base in case base test coverage is missing.
18 | COV_GO_VERSION: stable # Version of Go to collect coverage
19 | TARGET_DELTA_COV: 90 # Target coverage of changed lines, in percents
20 | jobs:
21 | test:
22 | strategy:
23 | matrix:
24 | go-version: [ 1.13.x, stable, oldstable ]
25 | runs-on: ubuntu-latest
26 | steps:
27 | - name: Install Go
28 | uses: actions/setup-go@v5
29 | with:
30 | go-version: ${{ matrix.go-version }}
31 |
32 | - name: Checkout code
33 | uses: actions/checkout@v4
34 |
35 | - name: Go cache
36 | uses: actions/cache@v4
37 | with:
38 | # In order:
39 | # * Module download cache
40 | # * Build cache (Linux)
41 | path: |
42 | ~/go/pkg/mod
43 | ~/.cache/go-build
44 | key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.sum') }}
45 | restore-keys: |
46 | ${{ runner.os }}-go-cache
47 |
48 | - name: Restore base test coverage
49 | id: base-coverage
50 | if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != ''
51 | uses: actions/cache@v4
52 | with:
53 | path: |
54 | unit-base.txt
55 | # Use base sha for PR or new commit hash for master/main push in test result key.
56 | key: ${{ runner.os }}-unit-test-coverage-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
57 |
58 | - name: Run test for base code
59 | if: matrix.go-version == env.COV_GO_VERSION && env.RUN_BASE_COVERAGE == 'on' && steps.base-coverage.outputs.cache-hit != 'true' && github.event.pull_request.base.sha != ''
60 | run: |
61 | git fetch origin master ${{ github.event.pull_request.base.sha }}
62 | HEAD=$(git rev-parse HEAD)
63 | git reset --hard ${{ github.event.pull_request.base.sha }}
64 | (make test-unit && go tool cover -func=./unit.coverprofile > unit-base.txt) || echo "No test-unit in base"
65 | git reset --hard $HEAD
66 |
67 | - name: Test
68 | id: test
69 | run: |
70 | make test-unit
71 | go tool cover -func=./unit.coverprofile > unit.txt
72 | TOTAL=$(grep 'total:' unit.txt)
73 | echo "${TOTAL}"
74 | echo "total=$TOTAL" >> $GITHUB_OUTPUT
75 |
76 | - name: Annotate missing test coverage
77 | id: annotate
78 | if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != ''
79 | run: |
80 | curl -sLO https://github.com/vearutop/gocovdiff/releases/download/v1.4.2/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz && rm linux_amd64.tar.gz
81 | gocovdiff_hash=$(git hash-object ./gocovdiff)
82 | [ "$gocovdiff_hash" == "c37862c73a677e5a9c069470287823ab5bbf0244" ] || (echo "::error::unexpected hash for gocovdiff, possible tampering: $gocovdiff_hash" && exit 1)
83 | git fetch origin master ${{ github.event.pull_request.base.sha }}
84 | REP=$(./gocovdiff -mod github.com/$GITHUB_REPOSITORY -cov unit.coverprofile -gha-annotations gha-unit.txt -delta-cov-file delta-cov-unit.txt -target-delta-cov ${TARGET_DELTA_COV})
85 | echo "${REP}"
86 | cat gha-unit.txt
87 | DIFF=$(test -e unit-base.txt && ./gocovdiff -mod github.com/$GITHUB_REPOSITORY -func-cov unit.txt -func-base-cov unit-base.txt || echo "Missing base coverage file")
88 | TOTAL=$(cat delta-cov-unit.txt)
89 | echo "rep<> $GITHUB_OUTPUT && echo "$REP" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT
90 | echo "diff<> $GITHUB_OUTPUT && echo "$DIFF" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT
91 | echo "total<> $GITHUB_OUTPUT && echo "$TOTAL" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT
92 |
93 | - name: Comment test coverage
94 | continue-on-error: true
95 | if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != ''
96 | uses: marocchino/sticky-pull-request-comment@v2
97 | with:
98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
99 | header: unit-test
100 | message: |
101 | ### Unit Test Coverage
102 | ${{ steps.test.outputs.total }}
103 | ${{ steps.annotate.outputs.total }}
104 | Coverage of changed lines
105 |
106 | ${{ steps.annotate.outputs.rep }}
107 |
108 |
109 |
110 | Coverage diff with base branch
111 |
112 | ${{ steps.annotate.outputs.diff }}
113 |
114 |
115 |
116 | - name: Store base coverage
117 | if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
118 | run: cp unit.txt unit-base.txt
119 |
120 | - name: Upload code coverage
121 | if: matrix.go-version == env.COV_GO_VERSION
122 | uses: codecov/codecov-action@v5
123 | with:
124 | files: ./unit.coverprofile
125 | flags: unittests
126 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /*.coverprofile
3 | /.vscode
4 | /bench-*.txt
5 | /vendor
6 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
2 | run:
3 | tests: true
4 |
5 | linters-settings:
6 | errcheck:
7 | check-type-assertions: true
8 | check-blank: true
9 | gocyclo:
10 | min-complexity: 45
11 | dupl:
12 | threshold: 100
13 | misspell:
14 | locale: US
15 | unparam:
16 | check-exported: true
17 | nestif:
18 | min-complexity: 7
19 |
20 | linters:
21 | enable-all: true
22 | disable:
23 | - fatcontext
24 | - err113
25 | - maintidx
26 | - musttag
27 | - containedctx
28 | - funlen
29 | - gocognit
30 | - cyclop
31 | - lll
32 | - gochecknoglobals
33 | - wrapcheck
34 | - paralleltest
35 | - forbidigo
36 | - forcetypeassert
37 | - varnamelen
38 | - tagliatelle
39 | - errname
40 | - ireturn
41 | - exhaustruct
42 | - nonamedreturns
43 | - testableexamples
44 | - dupword
45 | - depguard
46 | - tagalign
47 | - mnd
48 | - testifylint
49 | - recvcheck
50 |
51 | issues:
52 | exclude:
53 | - "strings.Title is deprecated"
54 | - "strings.Title has been deprecated"
55 | - "\"io/ioutil\" has been deprecated"
56 | - "cyclomatic complexity \\d+ of func `\\(Schema\\).IsTrivial` is high"
57 | exclude-use-default: false
58 | exclude-rules:
59 | - linters:
60 | - mnd
61 | - goconst
62 | - noctx
63 | - funlen
64 | - dupl
65 | - unused
66 | - unparam
67 | path: "_test.go"
68 | - linters:
69 | - errcheck # Error checking omitted for brevity.
70 | - errchkjson
71 | - gosec
72 | path: "example_"
73 | - linters:
74 | - revive
75 | text: "unused-parameter: parameter"
76 |
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 swaggest
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #GOLANGCI_LINT_VERSION := "v1.64.5" # Optional configuration to pinpoint golangci-lint version.
2 |
3 | # The head of Makefile determines location of dev-go to include standard targets.
4 | GO ?= go
5 | export GO111MODULE = on
6 |
7 | ifneq "$(GOFLAGS)" ""
8 | $(info GOFLAGS: ${GOFLAGS})
9 | endif
10 |
11 | ifneq "$(wildcard ./vendor )" ""
12 | $(info Using vendor)
13 | modVendor = -mod=vendor
14 | ifeq (,$(findstring -mod,$(GOFLAGS)))
15 | export GOFLAGS := ${GOFLAGS} ${modVendor}
16 | endif
17 | ifneq "$(wildcard ./vendor/github.com/bool64/dev)" ""
18 | DEVGO_PATH := ./vendor/github.com/bool64/dev
19 | endif
20 | endif
21 |
22 | ifeq ($(DEVGO_PATH),)
23 | DEVGO_PATH := $(shell GO111MODULE=on $(GO) list ${modVendor} -f '{{.Dir}}' -m github.com/bool64/dev)
24 | ifeq ($(DEVGO_PATH),)
25 | $(info Module github.com/bool64/dev not found, downloading.)
26 | DEVGO_PATH := $(shell export GO111MODULE=on && $(GO) get github.com/bool64/dev && $(GO) list -f '{{.Dir}}' -m github.com/bool64/dev)
27 | endif
28 | endif
29 |
30 | -include $(DEVGO_PATH)/makefiles/main.mk
31 | -include $(DEVGO_PATH)/makefiles/lint.mk
32 | -include $(DEVGO_PATH)/makefiles/test-unit.mk
33 | -include $(DEVGO_PATH)/makefiles/bench.mk
34 | -include $(DEVGO_PATH)/makefiles/reset-ci.mk
35 |
36 | # Add your custom targets here.
37 |
38 | ## Run tests
39 | test: test-unit
40 |
41 | JSON_CLI_VERSION := "v1.7.7"
42 |
43 | ## Generate JSON schema entities
44 | gen:
45 | @test -s $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) || (curl -sSfL https://github.com/swaggest/json-cli/releases/download/$(JSON_CLI_VERSION)/json-cli -o $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) && chmod +x $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION))
46 | @cd resources/schema/ && $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) gen-go draft-07.json --output ../../entities.go --package-name jsonschema --with-zero-values --fluent-setters --enable-default-additional-properties --with-tests --root-name SchemaOrBool \
47 | --renames CoreSchemaMetaSchema:Schema SimpleTypes:SimpleType SimpleTypeArray:Array SimpleTypeBoolean:Boolean SimpleTypeInteger:Integer SimpleTypeNull:Null SimpleTypeNumber:Number SimpleTypeObject:Object SimpleTypeString:String
48 | gofmt -w ./entities.go ./entities_test.go
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSON Schema structures for Go
2 |
3 |
4 |
5 | [](https://github.com/swaggest/jsonschema-go/actions?query=branch%3Amaster+workflow%3Atest-unit)
6 | [](https://codecov.io/gh/swaggest/jsonschema-go)
7 | [](https://pkg.go.dev/github.com/swaggest/jsonschema-go)
8 | [](https://wakatime.com/badge/github/swaggest/jsonschema-go)
9 | 
10 | 
11 |
12 | This library provides Go structures to marshal/unmarshal and reflect [JSON Schema](https://json-schema.org/) documents.
13 |
14 | ## Reflector
15 |
16 | [Documentation](https://pkg.go.dev/github.com/swaggest/jsonschema-go#Reflector.Reflect).
17 |
18 | ```go
19 | type MyStruct struct {
20 | Amount float64 `json:"amount" minimum:"10.5" example:"20.6" required:"true"`
21 | Abc string `json:"abc" pattern:"[abc]"`
22 | _ struct{} `additionalProperties:"false"` // Tags of unnamed field are applied to parent schema.
23 | _ struct{} `title:"My Struct" description:"Holds my data."` // Multiple unnamed fields can be used.
24 | }
25 |
26 | reflector := jsonschema.Reflector{}
27 |
28 | schema, err := reflector.Reflect(MyStruct{})
29 | if err != nil {
30 | log.Fatal(err)
31 | }
32 |
33 | j, err := json.MarshalIndent(schema, "", " ")
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 |
38 | fmt.Println(string(j))
39 |
40 | // Output:
41 | // {
42 | // "title": "My Struct",
43 | // "description": "Holds my data.",
44 | // "required": [
45 | // "amount"
46 | // ],
47 | // "additionalProperties": false,
48 | // "properties": {
49 | // "abc": {
50 | // "pattern": "[abc]",
51 | // "type": "string"
52 | // },
53 | // "amount": {
54 | // "examples": [
55 | // 20.6
56 | // ],
57 | // "minimum": 10.5,
58 | // "type": "number"
59 | // }
60 | // },
61 | // "type": "object"
62 | // }
63 | ```
64 |
65 | ## Customization
66 |
67 | By default, JSON Schema is generated from Go struct field types and tags.
68 | It works well for the majority of cases, but if it does not there are rich customization options.
69 |
70 | ### Field tags
71 |
72 | ```go
73 | type MyObj struct {
74 | BoundedNumber int `query:"boundedNumber" minimum:"-100" maximum:"100"`
75 | SpecialString string `json:"specialString" pattern:"^[a-z]{4}$" minLength:"4" maxLength:"4"`
76 | }
77 | ```
78 |
79 | Note: field tags are only applied to inline schemas, if you use named type then referenced schema
80 | will be created and tags will be ignored. This happens because referenced schema can be used in
81 | multiple fields with conflicting tags, therefore customization of referenced schema has to done on
82 | the type itself via `RawExposer`, `Exposer` or `Preparer`.
83 |
84 | Each tag value has to be put in double quotes (`"123"`).
85 |
86 | These tags can be used:
87 | * [`title`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.6.1), string
88 | * [`description`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.6.1), string
89 | * [`default`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.6.2), can be scalar or JSON value
90 | * [`example`](https://json-schema.org/draft/2020-12/json-schema-validation.html#name-examples), a scalar value that matches type of parent property, for an array it is applied to items
91 | * [`examples`](https://json-schema.org/draft/2020-12/json-schema-validation.html#name-examples), a JSON array value
92 | * [`const`](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.3), can be scalar or JSON value
93 | * [`deprecated`](https://json-schema.org/draft/2020-12/json-schema-validation#name-deprecated), boolean
94 | * [`readOnly`](https://json-schema.org/draft/2020-12/json-schema-validation#name-deprecated), boolean
95 | * [`writeOnly`](https://json-schema.org/draft/2020-12/json-schema-validation#name-deprecated), boolean
96 | * [`pattern`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.2.3), string
97 | * [`format`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.7), string
98 | * [`multipleOf`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.1.1), float > 0
99 | * [`maximum`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.1.2), float
100 | * [`minimum`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.1.3), float
101 | * [`maxLength`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.2.1), integer
102 | * [`minLength`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.2.2), integer
103 | * [`maxItems`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.3.2), integer
104 | * [`minItems`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.3.3), integer
105 | * [`maxProperties`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.4.1), integer
106 | * [`minProperties`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.4.2), integer
107 | * [`exclusiveMaximum`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.1.2), boolean
108 | * [`exclusiveMinimum`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.1.3), boolean
109 | * [`uniqueItems`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.3.4), boolean
110 | * [`enum`](https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5.5.1), tag value must be a JSON or comma-separated list of strings
111 | * `required`, boolean, marks property as required
112 | * `nullable`, boolean, overrides nullability of the property
113 |
114 | Unnamed fields can be used to configure parent schema:
115 |
116 | ```go
117 | type MyObj struct {
118 | BoundedNumber int `query:"boundedNumber" minimum:"-100" maximum:"100"`
119 | SpecialString string `json:"specialString" pattern:"^[a-z]{4}$" minLength:"4" maxLength:"4"`
120 | _ struct{} `additionalProperties:"false" description:"MyObj is my object."`
121 | }
122 | ```
123 |
124 | In case of a structure with multiple name tags, you can enable filtering of unnamed fields with
125 | ReflectContext.UnnamedFieldWithTag option and add matching name tags to structure (e.g. query:"_").
126 |
127 | ```go
128 | type MyObj struct {
129 | BoundedNumber int `query:"boundedNumber" minimum:"-100" maximum:"100"`
130 | SpecialString string `json:"specialString" pattern:"^[a-z]{4}$" minLength:"4" maxLength:"4"`
131 | // These parent schema tags would only be applied to `query` schema reflection (not for `json`).
132 | _ struct{} `query:"_" additionalProperties:"false" description:"MyObj is my object."`
133 | }
134 | ```
135 |
136 | Complex nested maps and slices/arrays can be configured with path-prefixed field tags.
137 |
138 | ```go
139 | type MyObj struct {
140 | Ints []int `json:"ints" items.title:"My int"`
141 | ExtraFloats [][]float64 `json:"extra_floats" items.items.title:"My float" items.items.enum:"1.23,4.56"`
142 | MappedStrings map[int]string `json:"mapped_strings" additionalProperties.enum:"abc,def"`
143 | VeryDeep map[int][]string `json:"very_deep" additionalProperties.items.enum:"abc,def"`
144 | }
145 | ```
146 |
147 | ### Implementing interfaces on a type
148 |
149 | There are a few interfaces that can be implemented on a type to customize JSON Schema generation.
150 |
151 | * [`Preparer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#Preparer) allows to change generated JSON Schema.
152 | * [`Exposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#Exposer) overrides generated JSON Schema.
153 | * [`RawExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#RawExposer) overrides generated JSON Schema.
154 | * [`Described`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#Described) exposes description.
155 | * [`Titled`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#Titled) exposes title.
156 | * [`Enum`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#Enum) exposes enum values.
157 | * [`NamedEnum`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#NamedEnum) exposes enum values with names.
158 | * [`SchemaInliner`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#SchemaInliner) inlines schema without creating a definition.
159 | * [`IgnoreTypeName`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#IgnoreTypeName), when implemented on a mapped type forces the use of original type for definition name.
160 | * [`EmbedReferencer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#EmbedReferencer), when implemented on an embedded field type, makes an `allOf` reference to that type definition.
161 |
162 | And a few interfaces to expose subschemas (`anyOf`, `allOf`, `oneOf`, `not` and `if`, `then`, `else`).
163 | * [`AnyOfExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#AnyOfExposer) exposes `anyOf` subschemas.
164 | * [`AllOfExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#AllOfExposer) exposes `allOf` subschemas.
165 | * [`OneOfExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#OneOfExposer) exposes `oneOf` subschemas.
166 | * [`NotExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#NotExposer) exposes `not` subschema.
167 | * [`IfExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#IfExposer) exposes `if` subschema.
168 | * [`ThenExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#ThenExposer) exposes `then` subschema.
169 | * [`ElseExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#ElseExposer) exposes `else` subschema.
170 |
171 | There are also helper functions
172 | [`jsonschema.AllOf`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#AllOf),
173 | [`jsonschema.AnyOf`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#AnyOf),
174 | [`jsonschema.OneOf`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#OneOf)
175 | to create exposer instance from multiple values.
176 |
177 |
178 |
179 | ### Configuring the reflector
180 |
181 | Additional centralized configuration is available with
182 | [`jsonschema.ReflectContext`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#ReflectContext) and
183 | [`Reflect`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#Reflector.Reflect) options.
184 |
185 | * [`CollectDefinitions`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#CollectDefinitions) disables definitions storage in schema and calls user function instead.
186 | * [`DefinitionsPrefix`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#DefinitionsPrefix) sets path prefix for definitions.
187 | * [`PropertyNameTag`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#PropertyNameTag) allows using field tags other than `json`.
188 | * [`InterceptSchema`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#InterceptSchema) called for every type during schema reflection.
189 | * [`InterceptProp`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#InterceptProp) called for every property during schema reflection.
190 | * [`InlineRefs`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#InlineRefs) tries to inline all references (instead of creating definitions).
191 | * [`RootNullable`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#RootNullable) enables nullability of root schema.
192 | * [`RootRef`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#RootRef) converts root schema to definition reference.
193 | * [`StripDefinitionNamePrefix`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#StripDefinitionNamePrefix) strips prefix from definition name.
194 | * [`PropertyNameMapping`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#PropertyNameMapping) explicit name mapping instead field tags.
195 | * [`ProcessWithoutTags`](https://pkg.go.dev/github.com/swaggest/jsonschema-go#ProcessWithoutTags) enables processing fields without any tags specified.
196 |
197 | ### Virtual structure
198 |
199 | Sometimes it is impossible to define a static Go `struct`, for example when fields are only known at runtime.
200 | Yet, you may need to include such fields in JSON schema reflection pipeline.
201 |
202 | For any reflected value, standalone or nested, you can define a virtual structure that would be treated as a native Go struct.
203 |
204 | ```go
205 | s := jsonschema.Struct{}
206 | s.SetTitle("Test title")
207 | s.SetDescription("Test description")
208 | s.DefName = "TestStruct"
209 | s.Nullable = true
210 |
211 | s.Fields = append(s.Fields, jsonschema.Field{
212 | Name: "Foo",
213 | Value: "abc",
214 | Tag: `json:"foo" minLength:"3"`,
215 | })
216 |
217 | r := jsonschema.Reflector{}
218 | schema, _ := r.Reflect(s)
219 | j, _ := assertjson.MarshalIndentCompact(schema, "", " ", 80)
220 |
221 | fmt.Println("Standalone:", string(j))
222 |
223 | type MyStruct struct {
224 | jsonschema.Struct // Can be embedded.
225 |
226 | Bar int `json:"bar"`
227 |
228 | Nested jsonschema.Struct `json:"nested"` // Can be nested.
229 | }
230 |
231 | ms := MyStruct{}
232 | ms.Nested = s
233 | ms.Struct = s
234 |
235 | schema, _ = r.Reflect(ms)
236 | j, _ = assertjson.MarshalIndentCompact(schema, "", " ", 80)
237 |
238 | fmt.Println("Nested:", string(j))
239 |
240 | // Output:
241 | // Standalone: {
242 | // "title":"Test title","description":"Test description",
243 | // "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
244 | // }
245 | // Nested: {
246 | // "definitions":{
247 | // "TestStruct":{
248 | // "title":"Test title","description":"Test description",
249 | // "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
250 | // }
251 | // },
252 | // "properties":{
253 | // "bar":{"type":"integer"},"foo":{"minLength":3,"type":"string"},
254 | // "nested":{"$ref":"#/definitions/TestStruct"}
255 | // },
256 | // "type":"object"
257 | // }
258 | ```
259 |
260 | ### Custom Tags For Schema Definitions
261 |
262 | If you're using additional libraries for validation, like for example
263 | [`go-playground/validator`](https://github.com/go-playground/validator), you may want to infer validation rules into
264 | documented JSON schema.
265 |
266 | ```go
267 | type My struct {
268 | Foo *string `json:"foo" validate:"required"`
269 | }
270 | ```
271 |
272 | Normally, `validate:"required"` is not recognized, and you'd need to add `required:"true"` to have the rule exported to
273 | JSON schema.
274 |
275 | However, it is possible to extend reflection with custom processing with `InterceptProp` option.
276 |
277 | ```go
278 | s, err := r.Reflect(My{}, jsonschema.InterceptProp(func(params jsonschema.InterceptPropParams) error {
279 | if !params.Processed {
280 | return nil
281 | }
282 |
283 | if v, ok := params.Field.Tag.Lookup("validate"); ok {
284 | if strings.Contains(v, "required") {
285 | params.ParentSchema.Required = append(params.ParentSchema.Required, params.Name)
286 | }
287 | }
288 |
289 | return nil
290 | }))
291 | ```
292 |
--------------------------------------------------------------------------------
/camelcase.go:
--------------------------------------------------------------------------------
1 | package jsonschema
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 | )
7 |
8 | var (
9 | numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
10 | numberReplacement = []byte(`$1 $2 $3`)
11 | )
12 |
13 | // toCamel converts a string to CamelCase.
14 | func toCamel(s string) string {
15 | b := numberSequence.ReplaceAll([]byte(s), numberReplacement)
16 | s = string(b)
17 | s = strings.Trim(s, " ")
18 | n := ""
19 | capNext := true
20 |
21 | for _, v := range s {
22 | if v >= 'A' && v <= 'Z' {
23 | n += string(v)
24 | }
25 |
26 | if v >= '0' && v <= '9' {
27 | n += string(v)
28 | }
29 |
30 | if v == '[' || v == ']' {
31 | n += string(v)
32 | }
33 |
34 | if v >= 'a' && v <= 'z' {
35 | if capNext {
36 | n += strings.ToUpper(string(v))
37 | } else {
38 | n += string(v)
39 | }
40 | }
41 |
42 | if v == '_' || v == ' ' || v == '-' || v == '.' || v == '[' || v == ']' || v == '·' {
43 | capNext = true
44 | } else {
45 | capNext = false
46 | }
47 | }
48 |
49 | return n
50 | }
51 |
--------------------------------------------------------------------------------
/context.go:
--------------------------------------------------------------------------------
1 | package jsonschema
2 |
3 | import (
4 | "context"
5 | "reflect"
6 | "strings"
7 |
8 | "github.com/swaggest/refl"
9 | )
10 |
11 | // CollectDefinitions enables collecting definitions with provided func instead of result schema.
12 | func CollectDefinitions(f func(name string, schema Schema)) func(*ReflectContext) {
13 | return func(rc *ReflectContext) {
14 | rc.CollectDefinitions = f
15 | }
16 | }
17 |
18 | // DefinitionsPrefix sets up location for newly created references, default "#/definitions/".
19 | func DefinitionsPrefix(prefix string) func(*ReflectContext) {
20 | return func(rc *ReflectContext) {
21 | rc.DefinitionsPrefix = prefix
22 | }
23 | }
24 |
25 | // PropertyNameTag sets up which field tag to use for property name, default "json".
26 | func PropertyNameTag(tag string, additional ...string) func(*ReflectContext) {
27 | return func(rc *ReflectContext) {
28 | rc.PropertyNameTag = tag
29 | rc.PropertyNameAdditionalTags = additional
30 | }
31 | }
32 |
33 | // InterceptTypeFunc can intercept type reflection to control or modify schema.
34 | //
35 | // True bool result demands no further processing for the Schema.
36 | //
37 | // Deprecated: use InterceptSchemaFunc.
38 | type InterceptTypeFunc func(reflect.Value, *Schema) (stop bool, err error)
39 |
40 | // InterceptSchemaFunc can intercept type reflection to control or modify schema.
41 | //
42 | // True bool result demands no further processing for the Schema.
43 | type InterceptSchemaFunc func(params InterceptSchemaParams) (stop bool, err error)
44 |
45 | // InterceptSchemaParams defines InterceptSchemaFunc parameters.
46 | //
47 | // Interceptor in invoked two times, before and after default schema processing.
48 | // If InterceptSchemaFunc returns true or fails, further processing and second invocation are skipped.
49 | type InterceptSchemaParams struct {
50 | Context *ReflectContext
51 | Value reflect.Value
52 | Schema *Schema
53 | Processed bool
54 | }
55 |
56 | // InterceptPropertyFunc can intercept field reflection to control or modify schema.
57 | //
58 | // Return ErrSkipProperty to avoid adding this property to parent Schema.Properties.
59 | // Pointer to parent Schema is available in propertySchema.Parent.
60 | //
61 | // Deprecated: use InterceptPropFunc.
62 | type InterceptPropertyFunc func(name string, field reflect.StructField, propertySchema *Schema) error
63 |
64 | // InterceptPropFunc can intercept field reflection to control or modify schema.
65 | //
66 | // Return ErrSkipProperty to avoid adding this property to parent Schema.Properties.
67 | // Pointer to parent Schema is available in propertySchema.Parent.
68 | type InterceptPropFunc func(params InterceptPropParams) error
69 |
70 | // InterceptPropParams defines InterceptPropFunc parameters.
71 | //
72 | // Interceptor in invoked two times, before and after default property schema processing.
73 | // If InterceptPropFunc fails, further processing and second invocation are skipped.
74 | type InterceptPropParams struct {
75 | Context *ReflectContext
76 | Path []string
77 | Name string
78 | Field reflect.StructField
79 | PropertySchema *Schema
80 | ParentSchema *Schema
81 | Processed bool
82 | }
83 |
84 | // InterceptNullabilityParams defines InterceptNullabilityFunc parameters.
85 | type InterceptNullabilityParams struct {
86 | Context *ReflectContext
87 | OrigSchema Schema
88 | Schema *Schema
89 | Type reflect.Type
90 | OmitEmpty bool
91 | NullAdded bool
92 | RefDef *Schema
93 | }
94 |
95 | // InterceptNullabilityFunc can intercept schema reflection to control or modify nullability state.
96 | // It is called after default nullability rules are applied.
97 | type InterceptNullabilityFunc func(params InterceptNullabilityParams)
98 |
99 | // InterceptNullability add hook to customize nullability.
100 | func InterceptNullability(f InterceptNullabilityFunc) func(reflectContext *ReflectContext) {
101 | return func(rc *ReflectContext) {
102 | if rc.InterceptNullability != nil {
103 | prev := rc.InterceptNullability
104 | rc.InterceptNullability = func(params InterceptNullabilityParams) {
105 | prev(params)
106 | f(params)
107 | }
108 | } else {
109 | rc.InterceptNullability = f
110 | }
111 | }
112 | }
113 |
114 | // InterceptType adds hook to customize schema.
115 | //
116 | // Deprecated: use InterceptSchema.
117 | func InterceptType(f InterceptTypeFunc) func(*ReflectContext) {
118 | return InterceptSchema(func(params InterceptSchemaParams) (stop bool, err error) {
119 | return f(params.Value, params.Schema)
120 | })
121 | }
122 |
123 | // InterceptSchema adds hook to customize schema.
124 | func InterceptSchema(f InterceptSchemaFunc) func(*ReflectContext) {
125 | return func(rc *ReflectContext) {
126 | if rc.interceptSchema != nil {
127 | prev := rc.interceptSchema
128 | rc.interceptSchema = func(params InterceptSchemaParams) (b bool, err error) {
129 | ret, err := prev(params)
130 | if err != nil || ret {
131 | return ret, err
132 | }
133 |
134 | return f(params)
135 | }
136 | } else {
137 | rc.interceptSchema = f
138 | }
139 | }
140 | }
141 |
142 | // InterceptProperty adds hook to customize property schema.
143 | //
144 | // Deprecated: use InterceptProp.
145 | func InterceptProperty(f InterceptPropertyFunc) func(*ReflectContext) {
146 | return InterceptProp(func(params InterceptPropParams) error {
147 | if !params.Processed {
148 | return nil
149 | }
150 |
151 | return f(params.Name, params.Field, params.PropertySchema)
152 | })
153 | }
154 |
155 | // InterceptProp adds a hook to customize property schema.
156 | func InterceptProp(f InterceptPropFunc) func(reflectContext *ReflectContext) {
157 | return func(rc *ReflectContext) {
158 | if rc.interceptProp != nil {
159 | prev := rc.interceptProp
160 | rc.interceptProp = func(params InterceptPropParams) error {
161 | err := prev(params)
162 | if err != nil {
163 | return err
164 | }
165 |
166 | return f(params)
167 | }
168 | } else {
169 | rc.interceptProp = f
170 | }
171 | }
172 | }
173 |
174 | // InterceptDefName allows modifying reflected definition names.
175 | func InterceptDefName(f func(t reflect.Type, defaultDefName string) string) func(reflectContext *ReflectContext) {
176 | return func(rc *ReflectContext) {
177 | if rc.DefName != nil {
178 | prev := rc.DefName
179 | rc.DefName = func(t reflect.Type, defaultDefName string) string {
180 | defaultDefName = prev(t, defaultDefName)
181 |
182 | return f(t, defaultDefName)
183 | }
184 | } else {
185 | rc.DefName = f
186 | }
187 | }
188 | }
189 |
190 | // InlineRefs prevents references.
191 | func InlineRefs(rc *ReflectContext) {
192 | rc.InlineRefs = true
193 | }
194 |
195 | // RootNullable enables nullability (by pointer) for root schema, disabled by default.
196 | func RootNullable(rc *ReflectContext) {
197 | rc.RootNullable = true
198 | }
199 |
200 | // RootRef enables referencing root schema.
201 | func RootRef(rc *ReflectContext) {
202 | rc.RootRef = true
203 | }
204 |
205 | // StripDefinitionNamePrefix checks if definition name has any of provided prefixes
206 | // and removes first encountered.
207 | func StripDefinitionNamePrefix(prefix ...string) func(rc *ReflectContext) {
208 | return func(rc *ReflectContext) {
209 | rc.DefName = func(_ reflect.Type, defaultDefName string) string {
210 | for _, p := range prefix {
211 | s := strings.TrimPrefix(defaultDefName, p)
212 | s = strings.ReplaceAll(s, "["+p, "[")
213 |
214 | if s != defaultDefName {
215 | return s
216 | }
217 | }
218 |
219 | return defaultDefName
220 | }
221 | }
222 | }
223 |
224 | // PropertyNameMapping enables property name mapping from a struct field name.
225 | func PropertyNameMapping(mapping map[string]string) func(rc *ReflectContext) {
226 | return func(rc *ReflectContext) {
227 | rc.PropertyNameMapping = mapping
228 | }
229 | }
230 |
231 | // ProcessWithoutTags enables processing fields without any tags specified.
232 | func ProcessWithoutTags(rc *ReflectContext) {
233 | rc.ProcessWithoutTags = true
234 | }
235 |
236 | // SkipEmbeddedMapsSlices disables shortcutting into embedded maps and slices.
237 | func SkipEmbeddedMapsSlices(rc *ReflectContext) {
238 | rc.SkipEmbeddedMapsSlices = true
239 | }
240 |
241 | // SkipUnsupportedProperties skips properties with unsupported types (func, chan, etc...) instead of failing.
242 | func SkipUnsupportedProperties(rc *ReflectContext) {
243 | rc.SkipUnsupportedProperties = true
244 | }
245 |
246 | // ReflectContext accompanies single reflect operation.
247 | type ReflectContext struct {
248 | // Context allows communicating user data between reflection steps.
249 | context.Context
250 |
251 | // DefName returns custom definition name for a type, can be nil.
252 | DefName func(t reflect.Type, defaultDefName string) string
253 |
254 | // CollectDefinitions is triggered when named schema is created, can be nil.
255 | // Non-empty CollectDefinitions disables collection of definitions into resulting schema.
256 | CollectDefinitions func(name string, schema Schema)
257 |
258 | // DefinitionsPrefix defines location of named schemas, default #/definitions/.
259 | DefinitionsPrefix string
260 |
261 | // PropertyNameTag enables property naming from a field tag, e.g. `header:"first_name"`.
262 | PropertyNameTag string
263 |
264 | // PropertyNameAdditionalTags enables property naming from first available of multiple tags
265 | // if PropertyNameTag was not found.
266 | PropertyNameAdditionalTags []string
267 |
268 | // PropertyNameMapping enables property name mapping from a struct field name, e.g. "FirstName":"first_name".
269 | // Only applicable to top-level properties (including embedded).
270 | PropertyNameMapping map[string]string
271 |
272 | // ProcessWithoutTags enables processing fields without any tags specified.
273 | ProcessWithoutTags bool
274 |
275 | // UnnamedFieldWithTag enables a requirement that name tag is present
276 | // when processing _ fields to set up parent schema, e.g.
277 | // _ struct{} `header:"_" additionalProperties:"false"`.
278 | UnnamedFieldWithTag bool
279 |
280 | // EnvelopNullability enables `anyOf` enveloping of "type":"null" instead of injecting into definition.
281 | EnvelopNullability bool
282 |
283 | // InlineRefs tries to inline all types without making references.
284 | InlineRefs bool
285 |
286 | // RootRef exposes root schema as reference.
287 | RootRef bool
288 |
289 | // RootNullable enables nullability (by pointer) for root schema, disabled by default.
290 | RootNullable bool
291 |
292 | // SkipEmbeddedMapsSlices disables shortcutting into embedded maps and slices.
293 | SkipEmbeddedMapsSlices bool
294 |
295 | // InterceptType is called before and after type processing.
296 | // So it may be called twice for the same type, first time with empty Schema and
297 | // second time with fully processed schema.
298 | //
299 | // Deprecated: use InterceptSchema.
300 | InterceptType InterceptTypeFunc
301 |
302 | // interceptSchema is called before and after type Schema processing.
303 | // So it may be called twice for the same type, first time with empty Schema and
304 | // second time with fully processed schema.
305 | interceptSchema InterceptSchemaFunc
306 |
307 | // Deprecated: Use interceptProp.
308 | InterceptProperty InterceptPropertyFunc
309 |
310 | interceptProp InterceptPropFunc
311 | InterceptNullability InterceptNullabilityFunc
312 |
313 | // SkipNonConstraints disables parsing of `default` and `example` field tags.
314 | SkipNonConstraints bool
315 |
316 | // SkipUnsupportedProperties skips properties with unsupported types (func, chan, etc...) instead of failing.
317 | SkipUnsupportedProperties bool
318 |
319 | Path []string
320 | definitions map[refl.TypeString]*Schema // list of all definition objects
321 | definitionRefs map[refl.TypeString]Ref
322 | typeCycles map[refl.TypeString]*Schema
323 | rootDefName string
324 | parentStructField *reflect.StructField
325 | parentTagPrefix string
326 | }
327 |
328 | func (rc *ReflectContext) getDefinition(ref string) *Schema {
329 | for ts, r := range rc.definitionRefs {
330 | if r.Path+r.Name == ref {
331 | return rc.definitions[ts]
332 | }
333 | }
334 |
335 | return &Schema{}
336 | }
337 |
338 | func (rc *ReflectContext) deprecatedFallback() {
339 | if rc.InterceptType != nil {
340 | f := rc.InterceptType
341 |
342 | InterceptSchema(func(params InterceptSchemaParams) (stop bool, err error) {
343 | return f(params.Value, params.Schema)
344 | })
345 |
346 | rc.InterceptType = nil
347 | }
348 |
349 | if rc.InterceptProperty != nil {
350 | f := rc.InterceptProperty
351 |
352 | InterceptProp(func(params InterceptPropParams) error {
353 | if !params.Processed {
354 | return nil
355 | }
356 |
357 | return f(params.Name, params.Field, params.PropertySchema)
358 | })
359 |
360 | rc.InterceptProperty = nil
361 | }
362 | }
363 |
--------------------------------------------------------------------------------
/date.go:
--------------------------------------------------------------------------------
1 | package jsonschema
2 |
3 | import (
4 | "encoding"
5 | "encoding/json"
6 | "time"
7 | )
8 |
9 | // DateLayout describes date format.
10 | const DateLayout = "2006-01-02"
11 |
12 | // Date is a date represented in YYYY-MM-DD format.
13 | type Date time.Time
14 |
15 | var (
16 | _ encoding.TextMarshaler = new(Date)
17 | _ encoding.TextUnmarshaler = new(Date)
18 | _ json.Marshaler = new(Date)
19 | _ json.Unmarshaler = new(Date)
20 | )
21 |
22 | // UnmarshalText loads date from a standard format value.
23 | func (d *Date) UnmarshalText(data []byte) error {
24 | t, err := time.Parse(DateLayout, string(data))
25 | if err != nil {
26 | return err
27 | }
28 |
29 | *d = Date(t)
30 |
31 | return nil
32 | }
33 |
34 | // MarshalText marshals date in standard format.
35 | func (d Date) MarshalText() ([]byte, error) {
36 | return []byte(time.Time(d).Format(DateLayout)), nil
37 | }
38 |
39 | // UnmarshalJSON unmarshals date in standard format.
40 | func (d *Date) UnmarshalJSON(data []byte) error {
41 | var s string
42 |
43 | err := json.Unmarshal(data, &s)
44 | if err != nil {
45 | return err
46 | }
47 |
48 | return d.UnmarshalText([]byte(s))
49 | }
50 |
51 | // MarshalJSON marshals date in standard format.
52 | func (d Date) MarshalJSON() ([]byte, error) {
53 | return json.Marshal(time.Time(d).Format(DateLayout))
54 | }
55 |
--------------------------------------------------------------------------------
/date_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | "github.com/swaggest/jsonschema-go"
9 | )
10 |
11 | func TestDate_MarshalText(t *testing.T) {
12 | var d jsonschema.Date
13 |
14 | require.NoError(t, d.UnmarshalText([]byte("2021-05-08")))
15 | b, err := d.MarshalText()
16 | require.NoError(t, err)
17 | assert.Equal(t, "2021-05-08", string(b))
18 |
19 | assert.Error(t, d.UnmarshalText([]byte("2021-05-088")))
20 | }
21 |
22 | func TestDate_MarshalJSON(t *testing.T) {
23 | var d jsonschema.Date
24 |
25 | require.NoError(t, d.UnmarshalJSON([]byte(`"2021-05-08"`)))
26 | b, err := d.MarshalJSON()
27 | require.NoError(t, err)
28 | assert.Equal(t, `"2021-05-08"`, string(b))
29 |
30 | assert.Error(t, d.UnmarshalJSON([]byte(`""2021-05-088"`)))
31 | }
32 |
--------------------------------------------------------------------------------
/dev_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import _ "github.com/bool64/dev" // Include CI/Dev scripts to project.
4 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | // Package jsonschema provides tools to work with JSON Schema.
2 | package jsonschema
3 |
--------------------------------------------------------------------------------
/entities.go:
--------------------------------------------------------------------------------
1 | // Code generated by github.com/swaggest/json-cli v1.7.7, DO NOT EDIT.
2 |
3 | // Package jsonschema contains JSON mapping structures.
4 | package jsonschema
5 |
6 | import (
7 | "bytes"
8 | "encoding/json"
9 | "errors"
10 | "fmt"
11 | "reflect"
12 | )
13 |
14 | // Schema structure is generated from "#[object]".
15 | //
16 | // Core schema meta-schema.
17 | type Schema struct {
18 | ID *string `json:"$id,omitempty"` // Format: uri-reference.
19 | Schema *string `json:"$schema,omitempty"` // Format: uri.
20 | Ref *string `json:"$ref,omitempty"` // Format: uri-reference.
21 | Comment *string `json:"$comment,omitempty"`
22 | Title *string `json:"title,omitempty"`
23 | Description *string `json:"description,omitempty"`
24 | Default *interface{} `json:"default,omitempty"`
25 | ReadOnly *bool `json:"readOnly,omitempty"`
26 | WriteOnly *bool `json:"writeOnly,omitempty"`
27 | Deprecated *bool `json:"deprecated,omitempty"`
28 | Examples []interface{} `json:"examples,omitempty"`
29 | MultipleOf *float64 `json:"multipleOf,omitempty"`
30 | Maximum *float64 `json:"maximum,omitempty"`
31 | ExclusiveMaximum *float64 `json:"exclusiveMaximum,omitempty"`
32 | Minimum *float64 `json:"minimum,omitempty"`
33 | ExclusiveMinimum *float64 `json:"exclusiveMinimum,omitempty"`
34 | MaxLength *int64 `json:"maxLength,omitempty"`
35 | MinLength int64 `json:"minLength,omitempty"`
36 | Pattern *string `json:"pattern,omitempty"` // Format: regex.
37 | AdditionalItems *SchemaOrBool `json:"additionalItems,omitempty"` // Core schema meta-schema.
38 | Items *Items `json:"items,omitempty"`
39 | MaxItems *int64 `json:"maxItems,omitempty"`
40 | MinItems int64 `json:"minItems,omitempty"`
41 | UniqueItems *bool `json:"uniqueItems,omitempty"`
42 | Contains *SchemaOrBool `json:"contains,omitempty"` // Core schema meta-schema.
43 | MaxProperties *int64 `json:"maxProperties,omitempty"`
44 | MinProperties int64 `json:"minProperties,omitempty"`
45 | Required []string `json:"required,omitempty"`
46 | AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitempty"` // Core schema meta-schema.
47 | Definitions map[string]SchemaOrBool `json:"definitions,omitempty"`
48 | Properties map[string]SchemaOrBool `json:"properties,omitempty"`
49 | PatternProperties map[string]SchemaOrBool `json:"patternProperties,omitempty"`
50 | Dependencies map[string]DependenciesAdditionalProperties `json:"dependencies,omitempty"`
51 | PropertyNames *SchemaOrBool `json:"propertyNames,omitempty"` // Core schema meta-schema.
52 | Const *interface{} `json:"const,omitempty"`
53 | Enum []interface{} `json:"enum,omitempty"`
54 | Type *Type `json:"type,omitempty"`
55 | Format *string `json:"format,omitempty"`
56 | ContentMediaType *string `json:"contentMediaType,omitempty"`
57 | ContentEncoding *string `json:"contentEncoding,omitempty"`
58 | If *SchemaOrBool `json:"if,omitempty"` // Core schema meta-schema.
59 | Then *SchemaOrBool `json:"then,omitempty"` // Core schema meta-schema.
60 | Else *SchemaOrBool `json:"else,omitempty"` // Core schema meta-schema.
61 | AllOf []SchemaOrBool `json:"allOf,omitempty"`
62 | AnyOf []SchemaOrBool `json:"anyOf,omitempty"`
63 | OneOf []SchemaOrBool `json:"oneOf,omitempty"`
64 | Not *SchemaOrBool `json:"not,omitempty"` // Core schema meta-schema.
65 | ExtraProperties map[string]interface{} `json:"-"` // All unmatched properties.
66 | ReflectType reflect.Type `json:"-"`
67 | Parent *Schema `json:"-"`
68 | }
69 |
70 | // WithID sets ID value.
71 | func (s *Schema) WithID(val string) *Schema {
72 | s.ID = &val
73 | return s
74 | }
75 |
76 | // WithSchema sets Schema value.
77 | func (s *Schema) WithSchema(val string) *Schema {
78 | s.Schema = &val
79 | return s
80 | }
81 |
82 | // WithRef sets Ref value.
83 | func (s *Schema) WithRef(val string) *Schema {
84 | s.Ref = &val
85 | return s
86 | }
87 |
88 | // WithComment sets Comment value.
89 | func (s *Schema) WithComment(val string) *Schema {
90 | s.Comment = &val
91 | return s
92 | }
93 |
94 | // WithTitle sets Title value.
95 | func (s *Schema) WithTitle(val string) *Schema {
96 | s.Title = &val
97 | return s
98 | }
99 |
100 | // WithDescription sets Description value.
101 | func (s *Schema) WithDescription(val string) *Schema {
102 | s.Description = &val
103 | return s
104 | }
105 |
106 | // WithDefault sets Default value.
107 | func (s *Schema) WithDefault(val interface{}) *Schema {
108 | s.Default = &val
109 | return s
110 | }
111 |
112 | // WithReadOnly sets ReadOnly value.
113 | func (s *Schema) WithReadOnly(val bool) *Schema {
114 | s.ReadOnly = &val
115 | return s
116 | }
117 |
118 | // WithWriteOnly sets WriteOnly value.
119 | func (s *Schema) WithWriteOnly(val bool) *Schema {
120 | s.WriteOnly = &val
121 | return s
122 | }
123 |
124 | // WithDeprecated sets Deprecated value.
125 | func (s *Schema) WithDeprecated(val bool) *Schema {
126 | s.Deprecated = &val
127 | return s
128 | }
129 |
130 | // WithExamples sets Examples value.
131 | func (s *Schema) WithExamples(val ...interface{}) *Schema {
132 | s.Examples = val
133 | return s
134 | }
135 |
136 | // WithMultipleOf sets MultipleOf value.
137 | func (s *Schema) WithMultipleOf(val float64) *Schema {
138 | s.MultipleOf = &val
139 | return s
140 | }
141 |
142 | // WithMaximum sets Maximum value.
143 | func (s *Schema) WithMaximum(val float64) *Schema {
144 | s.Maximum = &val
145 | return s
146 | }
147 |
148 | // WithExclusiveMaximum sets ExclusiveMaximum value.
149 | func (s *Schema) WithExclusiveMaximum(val float64) *Schema {
150 | s.ExclusiveMaximum = &val
151 | return s
152 | }
153 |
154 | // WithMinimum sets Minimum value.
155 | func (s *Schema) WithMinimum(val float64) *Schema {
156 | s.Minimum = &val
157 | return s
158 | }
159 |
160 | // WithExclusiveMinimum sets ExclusiveMinimum value.
161 | func (s *Schema) WithExclusiveMinimum(val float64) *Schema {
162 | s.ExclusiveMinimum = &val
163 | return s
164 | }
165 |
166 | // WithMaxLength sets MaxLength value.
167 | func (s *Schema) WithMaxLength(val int64) *Schema {
168 | s.MaxLength = &val
169 | return s
170 | }
171 |
172 | // WithMinLength sets MinLength value.
173 | func (s *Schema) WithMinLength(val int64) *Schema {
174 | s.MinLength = val
175 | return s
176 | }
177 |
178 | // WithPattern sets Pattern value.
179 | func (s *Schema) WithPattern(val string) *Schema {
180 | s.Pattern = &val
181 | return s
182 | }
183 |
184 | // WithAdditionalItems sets AdditionalItems value.
185 | func (s *Schema) WithAdditionalItems(val SchemaOrBool) *Schema {
186 | s.AdditionalItems = &val
187 | return s
188 | }
189 |
190 | // AdditionalItemsEns ensures returned AdditionalItems is not nil.
191 | func (s *Schema) AdditionalItemsEns() *SchemaOrBool {
192 | if s.AdditionalItems == nil {
193 | s.AdditionalItems = new(SchemaOrBool)
194 | }
195 |
196 | return s.AdditionalItems
197 | }
198 |
199 | // WithItems sets Items value.
200 | func (s *Schema) WithItems(val Items) *Schema {
201 | s.Items = &val
202 | return s
203 | }
204 |
205 | // ItemsEns ensures returned Items is not nil.
206 | func (s *Schema) ItemsEns() *Items {
207 | if s.Items == nil {
208 | s.Items = new(Items)
209 | }
210 |
211 | return s.Items
212 | }
213 |
214 | // WithMaxItems sets MaxItems value.
215 | func (s *Schema) WithMaxItems(val int64) *Schema {
216 | s.MaxItems = &val
217 | return s
218 | }
219 |
220 | // WithMinItems sets MinItems value.
221 | func (s *Schema) WithMinItems(val int64) *Schema {
222 | s.MinItems = val
223 | return s
224 | }
225 |
226 | // WithUniqueItems sets UniqueItems value.
227 | func (s *Schema) WithUniqueItems(val bool) *Schema {
228 | s.UniqueItems = &val
229 | return s
230 | }
231 |
232 | // WithContains sets Contains value.
233 | func (s *Schema) WithContains(val SchemaOrBool) *Schema {
234 | s.Contains = &val
235 | return s
236 | }
237 |
238 | // ContainsEns ensures returned Contains is not nil.
239 | func (s *Schema) ContainsEns() *SchemaOrBool {
240 | if s.Contains == nil {
241 | s.Contains = new(SchemaOrBool)
242 | }
243 |
244 | return s.Contains
245 | }
246 |
247 | // WithMaxProperties sets MaxProperties value.
248 | func (s *Schema) WithMaxProperties(val int64) *Schema {
249 | s.MaxProperties = &val
250 | return s
251 | }
252 |
253 | // WithMinProperties sets MinProperties value.
254 | func (s *Schema) WithMinProperties(val int64) *Schema {
255 | s.MinProperties = val
256 | return s
257 | }
258 |
259 | // WithRequired sets Required value.
260 | func (s *Schema) WithRequired(val ...string) *Schema {
261 | s.Required = val
262 | return s
263 | }
264 |
265 | // WithAdditionalProperties sets AdditionalProperties value.
266 | func (s *Schema) WithAdditionalProperties(val SchemaOrBool) *Schema {
267 | s.AdditionalProperties = &val
268 | return s
269 | }
270 |
271 | // AdditionalPropertiesEns ensures returned AdditionalProperties is not nil.
272 | func (s *Schema) AdditionalPropertiesEns() *SchemaOrBool {
273 | if s.AdditionalProperties == nil {
274 | s.AdditionalProperties = new(SchemaOrBool)
275 | }
276 |
277 | return s.AdditionalProperties
278 | }
279 |
280 | // WithDefinitions sets Definitions value.
281 | func (s *Schema) WithDefinitions(val map[string]SchemaOrBool) *Schema {
282 | s.Definitions = val
283 | return s
284 | }
285 |
286 | // WithDefinitionsItem sets Definitions item value.
287 | func (s *Schema) WithDefinitionsItem(key string, val SchemaOrBool) *Schema {
288 | if s.Definitions == nil {
289 | s.Definitions = make(map[string]SchemaOrBool, 1)
290 | }
291 |
292 | s.Definitions[key] = val
293 |
294 | return s
295 | }
296 |
297 | // WithProperties sets Properties value.
298 | func (s *Schema) WithProperties(val map[string]SchemaOrBool) *Schema {
299 | s.Properties = val
300 | return s
301 | }
302 |
303 | // WithPropertiesItem sets Properties item value.
304 | func (s *Schema) WithPropertiesItem(key string, val SchemaOrBool) *Schema {
305 | if s.Properties == nil {
306 | s.Properties = make(map[string]SchemaOrBool, 1)
307 | }
308 |
309 | s.Properties[key] = val
310 |
311 | return s
312 | }
313 |
314 | // WithPatternProperties sets PatternProperties value.
315 | func (s *Schema) WithPatternProperties(val map[string]SchemaOrBool) *Schema {
316 | s.PatternProperties = val
317 | return s
318 | }
319 |
320 | // WithPatternPropertiesItem sets PatternProperties item value.
321 | func (s *Schema) WithPatternPropertiesItem(key string, val SchemaOrBool) *Schema {
322 | if s.PatternProperties == nil {
323 | s.PatternProperties = make(map[string]SchemaOrBool, 1)
324 | }
325 |
326 | s.PatternProperties[key] = val
327 |
328 | return s
329 | }
330 |
331 | // WithDependencies sets Dependencies value.
332 | func (s *Schema) WithDependencies(val map[string]DependenciesAdditionalProperties) *Schema {
333 | s.Dependencies = val
334 | return s
335 | }
336 |
337 | // WithDependenciesItem sets Dependencies item value.
338 | func (s *Schema) WithDependenciesItem(key string, val DependenciesAdditionalProperties) *Schema {
339 | if s.Dependencies == nil {
340 | s.Dependencies = make(map[string]DependenciesAdditionalProperties, 1)
341 | }
342 |
343 | s.Dependencies[key] = val
344 |
345 | return s
346 | }
347 |
348 | // WithPropertyNames sets PropertyNames value.
349 | func (s *Schema) WithPropertyNames(val SchemaOrBool) *Schema {
350 | s.PropertyNames = &val
351 | return s
352 | }
353 |
354 | // PropertyNamesEns ensures returned PropertyNames is not nil.
355 | func (s *Schema) PropertyNamesEns() *SchemaOrBool {
356 | if s.PropertyNames == nil {
357 | s.PropertyNames = new(SchemaOrBool)
358 | }
359 |
360 | return s.PropertyNames
361 | }
362 |
363 | // WithConst sets Const value.
364 | func (s *Schema) WithConst(val interface{}) *Schema {
365 | s.Const = &val
366 | return s
367 | }
368 |
369 | // WithEnum sets Enum value.
370 | func (s *Schema) WithEnum(val ...interface{}) *Schema {
371 | s.Enum = val
372 | return s
373 | }
374 |
375 | // WithType sets Type value.
376 | func (s *Schema) WithType(val Type) *Schema {
377 | s.Type = &val
378 | return s
379 | }
380 |
381 | // TypeEns ensures returned Type is not nil.
382 | func (s *Schema) TypeEns() *Type {
383 | if s.Type == nil {
384 | s.Type = new(Type)
385 | }
386 |
387 | return s.Type
388 | }
389 |
390 | // WithFormat sets Format value.
391 | func (s *Schema) WithFormat(val string) *Schema {
392 | s.Format = &val
393 | return s
394 | }
395 |
396 | // WithContentMediaType sets ContentMediaType value.
397 | func (s *Schema) WithContentMediaType(val string) *Schema {
398 | s.ContentMediaType = &val
399 | return s
400 | }
401 |
402 | // WithContentEncoding sets ContentEncoding value.
403 | func (s *Schema) WithContentEncoding(val string) *Schema {
404 | s.ContentEncoding = &val
405 | return s
406 | }
407 |
408 | // WithIf sets If value.
409 | func (s *Schema) WithIf(val SchemaOrBool) *Schema {
410 | s.If = &val
411 | return s
412 | }
413 |
414 | // IfEns ensures returned If is not nil.
415 | func (s *Schema) IfEns() *SchemaOrBool {
416 | if s.If == nil {
417 | s.If = new(SchemaOrBool)
418 | }
419 |
420 | return s.If
421 | }
422 |
423 | // WithThen sets Then value.
424 | func (s *Schema) WithThen(val SchemaOrBool) *Schema {
425 | s.Then = &val
426 | return s
427 | }
428 |
429 | // ThenEns ensures returned Then is not nil.
430 | func (s *Schema) ThenEns() *SchemaOrBool {
431 | if s.Then == nil {
432 | s.Then = new(SchemaOrBool)
433 | }
434 |
435 | return s.Then
436 | }
437 |
438 | // WithElse sets Else value.
439 | func (s *Schema) WithElse(val SchemaOrBool) *Schema {
440 | s.Else = &val
441 | return s
442 | }
443 |
444 | // ElseEns ensures returned Else is not nil.
445 | func (s *Schema) ElseEns() *SchemaOrBool {
446 | if s.Else == nil {
447 | s.Else = new(SchemaOrBool)
448 | }
449 |
450 | return s.Else
451 | }
452 |
453 | // WithAllOf sets AllOf value.
454 | func (s *Schema) WithAllOf(val ...SchemaOrBool) *Schema {
455 | s.AllOf = val
456 | return s
457 | }
458 |
459 | // WithAnyOf sets AnyOf value.
460 | func (s *Schema) WithAnyOf(val ...SchemaOrBool) *Schema {
461 | s.AnyOf = val
462 | return s
463 | }
464 |
465 | // WithOneOf sets OneOf value.
466 | func (s *Schema) WithOneOf(val ...SchemaOrBool) *Schema {
467 | s.OneOf = val
468 | return s
469 | }
470 |
471 | // WithNot sets Not value.
472 | func (s *Schema) WithNot(val SchemaOrBool) *Schema {
473 | s.Not = &val
474 | return s
475 | }
476 |
477 | // NotEns ensures returned Not is not nil.
478 | func (s *Schema) NotEns() *SchemaOrBool {
479 | if s.Not == nil {
480 | s.Not = new(SchemaOrBool)
481 | }
482 |
483 | return s.Not
484 | }
485 |
486 | // WithExtraProperties sets ExtraProperties value.
487 | func (s *Schema) WithExtraProperties(val map[string]interface{}) *Schema {
488 | s.ExtraProperties = val
489 | return s
490 | }
491 |
492 | // WithExtraPropertiesItem sets ExtraProperties item value.
493 | func (s *Schema) WithExtraPropertiesItem(key string, val interface{}) *Schema {
494 | if s.ExtraProperties == nil {
495 | s.ExtraProperties = make(map[string]interface{}, 1)
496 | }
497 |
498 | s.ExtraProperties[key] = val
499 |
500 | return s
501 | }
502 |
503 | type marshalSchema Schema
504 |
505 | var knownKeysSchema = []string{
506 | "$id",
507 | "$schema",
508 | "$ref",
509 | "$comment",
510 | "title",
511 | "description",
512 | "default",
513 | "readOnly",
514 | "examples",
515 | "multipleOf",
516 | "maximum",
517 | "exclusiveMaximum",
518 | "minimum",
519 | "exclusiveMinimum",
520 | "maxLength",
521 | "minLength",
522 | "pattern",
523 | "additionalItems",
524 | "items",
525 | "maxItems",
526 | "minItems",
527 | "uniqueItems",
528 | "contains",
529 | "maxProperties",
530 | "minProperties",
531 | "required",
532 | "additionalProperties",
533 | "definitions",
534 | "properties",
535 | "patternProperties",
536 | "dependencies",
537 | "propertyNames",
538 | "const",
539 | "enum",
540 | "type",
541 | "format",
542 | "contentMediaType",
543 | "contentEncoding",
544 | "if",
545 | "then",
546 | "else",
547 | "allOf",
548 | "anyOf",
549 | "oneOf",
550 | "not",
551 | }
552 |
553 | // UnmarshalJSON decodes JSON.
554 | func (s *Schema) UnmarshalJSON(data []byte) error {
555 | var err error
556 |
557 | ms := marshalSchema(*s)
558 |
559 | err = json.Unmarshal(data, &ms)
560 | if err != nil {
561 | return err
562 | }
563 |
564 | var rawMap map[string]json.RawMessage
565 |
566 | err = json.Unmarshal(data, &rawMap)
567 | if err != nil {
568 | rawMap = nil
569 | }
570 |
571 | if ms.Default == nil {
572 | if _, ok := rawMap["default"]; ok {
573 | var v interface{}
574 | ms.Default = &v
575 | }
576 | }
577 |
578 | if ms.Const == nil {
579 | if _, ok := rawMap["const"]; ok {
580 | var v interface{}
581 | ms.Const = &v
582 | }
583 | }
584 |
585 | for _, key := range knownKeysSchema {
586 | delete(rawMap, key)
587 | }
588 |
589 | for key, rawValue := range rawMap {
590 | if ms.ExtraProperties == nil {
591 | ms.ExtraProperties = make(map[string]interface{}, 1)
592 | }
593 |
594 | var val interface{}
595 |
596 | err = json.Unmarshal(rawValue, &val)
597 | if err != nil {
598 | return err
599 | }
600 |
601 | ms.ExtraProperties[key] = val
602 | }
603 |
604 | *s = Schema(ms)
605 |
606 | return nil
607 | }
608 |
609 | // MarshalJSON encodes JSON.
610 | func (s Schema) MarshalJSON() ([]byte, error) {
611 | if len(s.ExtraProperties) == 0 {
612 | return json.Marshal(marshalSchema(s))
613 | }
614 |
615 | return marshalUnion(marshalSchema(s), s.ExtraProperties)
616 | }
617 |
618 | // SchemaOrBool structure is generated from "#".
619 | //
620 | // Core schema meta-schema.
621 | type SchemaOrBool struct {
622 | TypeObject *Schema `json:"-"`
623 | TypeBoolean *bool `json:"-"`
624 | }
625 |
626 | // WithTypeObject sets TypeObject value.
627 | func (s *SchemaOrBool) WithTypeObject(val Schema) *SchemaOrBool {
628 | s.TypeObject = &val
629 | return s
630 | }
631 |
632 | // TypeObjectEns ensures returned TypeObject is not nil.
633 | func (s *SchemaOrBool) TypeObjectEns() *Schema {
634 | if s.TypeObject == nil {
635 | s.TypeObject = new(Schema)
636 | }
637 |
638 | return s.TypeObject
639 | }
640 |
641 | // WithTypeBoolean sets TypeBoolean value.
642 | func (s *SchemaOrBool) WithTypeBoolean(val bool) *SchemaOrBool {
643 | s.TypeBoolean = &val
644 | return s
645 | }
646 |
647 | // UnmarshalJSON decodes JSON.
648 | func (s *SchemaOrBool) UnmarshalJSON(data []byte) error {
649 | var err error
650 |
651 | typeValid := false
652 |
653 | if !typeValid {
654 | err = json.Unmarshal(data, &s.TypeObject)
655 | if err != nil {
656 | s.TypeObject = nil
657 | } else {
658 | typeValid = true
659 | }
660 | }
661 |
662 | if !typeValid {
663 | err = json.Unmarshal(data, &s.TypeBoolean)
664 | if err != nil {
665 | s.TypeBoolean = nil
666 | } else {
667 | typeValid = true
668 | }
669 | }
670 |
671 | if !typeValid {
672 | return err
673 | }
674 |
675 | return nil
676 | }
677 |
678 | // MarshalJSON encodes JSON.
679 | func (s SchemaOrBool) MarshalJSON() ([]byte, error) {
680 | switch {
681 | case s.TypeObject != nil:
682 | return json.Marshal(s.TypeObject)
683 | case s.TypeBoolean != nil:
684 | return json.Marshal(s.TypeBoolean)
685 | }
686 | return nil, errors.New("missing typed value")
687 | }
688 |
689 | // Items structure is generated from "#[object]->items".
690 | type Items struct {
691 | SchemaOrBool *SchemaOrBool `json:"-"`
692 | SchemaArray []SchemaOrBool `json:"-"`
693 | }
694 |
695 | // WithSchemaOrBool sets SchemaOrBool value.
696 | func (i *Items) WithSchemaOrBool(val SchemaOrBool) *Items {
697 | i.SchemaOrBool = &val
698 | return i
699 | }
700 |
701 | // SchemaOrBoolEns ensures returned SchemaOrBool is not nil.
702 | func (i *Items) SchemaOrBoolEns() *SchemaOrBool {
703 | if i.SchemaOrBool == nil {
704 | i.SchemaOrBool = new(SchemaOrBool)
705 | }
706 |
707 | return i.SchemaOrBool
708 | }
709 |
710 | // WithSchemaArray sets SchemaArray value.
711 | func (i *Items) WithSchemaArray(val ...SchemaOrBool) *Items {
712 | i.SchemaArray = val
713 | return i
714 | }
715 |
716 | // UnmarshalJSON decodes JSON.
717 | func (i *Items) UnmarshalJSON(data []byte) error {
718 | var err error
719 |
720 | anyOfErrors := make(map[string]error, 2)
721 | anyOfValid := 0
722 |
723 | err = json.Unmarshal(data, &i.SchemaOrBool)
724 | if err != nil {
725 | anyOfErrors["SchemaOrBool"] = err
726 | i.SchemaOrBool = nil
727 | } else {
728 | anyOfValid++
729 | }
730 |
731 | err = json.Unmarshal(data, &i.SchemaArray)
732 | if err != nil {
733 | anyOfErrors["SchemaArray"] = err
734 | i.SchemaArray = nil
735 | } else {
736 | anyOfValid++
737 | }
738 |
739 | if anyOfValid == 0 {
740 | return fmt.Errorf("anyOf constraint for Items failed with %d valid results: %v", anyOfValid, anyOfErrors)
741 | }
742 |
743 | return nil
744 | }
745 |
746 | // MarshalJSON encodes JSON.
747 | func (i Items) MarshalJSON() ([]byte, error) {
748 | return marshalUnion(i.SchemaOrBool, i.SchemaArray)
749 | }
750 |
751 | // DependenciesAdditionalProperties structure is generated from "#[object]->dependencies->additionalProperties".
752 | type DependenciesAdditionalProperties struct {
753 | SchemaOrBool *SchemaOrBool `json:"-"`
754 | StringArray []string `json:"-"`
755 | }
756 |
757 | // WithSchemaOrBool sets SchemaOrBool value.
758 | func (d *DependenciesAdditionalProperties) WithSchemaOrBool(val SchemaOrBool) *DependenciesAdditionalProperties {
759 | d.SchemaOrBool = &val
760 | return d
761 | }
762 |
763 | // SchemaOrBoolEns ensures returned SchemaOrBool is not nil.
764 | func (d *DependenciesAdditionalProperties) SchemaOrBoolEns() *SchemaOrBool {
765 | if d.SchemaOrBool == nil {
766 | d.SchemaOrBool = new(SchemaOrBool)
767 | }
768 |
769 | return d.SchemaOrBool
770 | }
771 |
772 | // WithStringArray sets StringArray value.
773 | func (d *DependenciesAdditionalProperties) WithStringArray(val ...string) *DependenciesAdditionalProperties {
774 | d.StringArray = val
775 | return d
776 | }
777 |
778 | // UnmarshalJSON decodes JSON.
779 | func (d *DependenciesAdditionalProperties) UnmarshalJSON(data []byte) error {
780 | var err error
781 |
782 | anyOfErrors := make(map[string]error, 2)
783 | anyOfValid := 0
784 |
785 | err = json.Unmarshal(data, &d.SchemaOrBool)
786 | if err != nil {
787 | anyOfErrors["SchemaOrBool"] = err
788 | d.SchemaOrBool = nil
789 | } else {
790 | anyOfValid++
791 | }
792 |
793 | err = json.Unmarshal(data, &d.StringArray)
794 | if err != nil {
795 | anyOfErrors["StringArray"] = err
796 | d.StringArray = nil
797 | } else {
798 | anyOfValid++
799 | }
800 |
801 | if anyOfValid == 0 {
802 | return fmt.Errorf("anyOf constraint for DependenciesAdditionalProperties failed with %d valid results: %v", anyOfValid, anyOfErrors)
803 | }
804 |
805 | return nil
806 | }
807 |
808 | // MarshalJSON encodes JSON.
809 | func (d DependenciesAdditionalProperties) MarshalJSON() ([]byte, error) {
810 | return marshalUnion(d.SchemaOrBool, d.StringArray)
811 | }
812 |
813 | // Type structure is generated from "#[object]->type".
814 | type Type struct {
815 | SimpleTypes *SimpleType `json:"-"`
816 | SliceOfSimpleTypeValues []SimpleType `json:"-"`
817 | }
818 |
819 | // WithSimpleTypes sets SimpleTypes value.
820 | func (t *Type) WithSimpleTypes(val SimpleType) *Type {
821 | t.SimpleTypes = &val
822 | return t
823 | }
824 |
825 | // WithSliceOfSimpleTypeValues sets SliceOfSimpleTypeValues value.
826 | func (t *Type) WithSliceOfSimpleTypeValues(val ...SimpleType) *Type {
827 | t.SliceOfSimpleTypeValues = val
828 | return t
829 | }
830 |
831 | // UnmarshalJSON decodes JSON.
832 | func (t *Type) UnmarshalJSON(data []byte) error {
833 | var err error
834 |
835 | anyOfErrors := make(map[string]error, 2)
836 | anyOfValid := 0
837 |
838 | err = json.Unmarshal(data, &t.SimpleTypes)
839 | if err != nil {
840 | anyOfErrors["SimpleTypes"] = err
841 | t.SimpleTypes = nil
842 | } else {
843 | anyOfValid++
844 | }
845 |
846 | err = json.Unmarshal(data, &t.SliceOfSimpleTypeValues)
847 | if err != nil {
848 | anyOfErrors["SliceOfSimpleTypeValues"] = err
849 | t.SliceOfSimpleTypeValues = nil
850 | } else {
851 | anyOfValid++
852 | }
853 |
854 | if anyOfValid == 0 {
855 | return fmt.Errorf("anyOf constraint for Type failed with %d valid results: %v", anyOfValid, anyOfErrors)
856 | }
857 |
858 | return nil
859 | }
860 |
861 | // MarshalJSON encodes JSON.
862 | func (t Type) MarshalJSON() ([]byte, error) {
863 | return marshalUnion(t.SimpleTypes, t.SliceOfSimpleTypeValues)
864 | }
865 |
866 | // SimpleType is an enum type.
867 | type SimpleType string
868 |
869 | // SimpleType values enumeration.
870 | const (
871 | Array = SimpleType("array")
872 | Boolean = SimpleType("boolean")
873 | Integer = SimpleType("integer")
874 | Null = SimpleType("null")
875 | Number = SimpleType("number")
876 | Object = SimpleType("object")
877 | String = SimpleType("string")
878 | )
879 |
880 | // MarshalJSON encodes JSON.
881 | func (i SimpleType) MarshalJSON() ([]byte, error) {
882 | switch i {
883 | case Array:
884 | case Boolean:
885 | case Integer:
886 | case Null:
887 | case Number:
888 | case Object:
889 | case String:
890 |
891 | default:
892 | return nil, fmt.Errorf("unexpected SimpleType value: %v", i)
893 | }
894 |
895 | return json.Marshal(string(i))
896 | }
897 |
898 | // UnmarshalJSON decodes JSON.
899 | func (i *SimpleType) UnmarshalJSON(data []byte) error {
900 | var ii string
901 |
902 | err := json.Unmarshal(data, &ii)
903 | if err != nil {
904 | return err
905 | }
906 |
907 | v := SimpleType(ii)
908 |
909 | switch v {
910 | case Array:
911 | case Boolean:
912 | case Integer:
913 | case Null:
914 | case Number:
915 | case Object:
916 | case String:
917 |
918 | default:
919 | return fmt.Errorf("unexpected SimpleType value: %v", v)
920 | }
921 |
922 | *i = v
923 |
924 | return nil
925 | }
926 |
927 | func marshalUnion(maps ...interface{}) ([]byte, error) {
928 | result := []byte("{")
929 | isObject := true
930 |
931 | for _, m := range maps {
932 | j, err := json.Marshal(m)
933 | if err != nil {
934 | return nil, err
935 | }
936 |
937 | if string(j) == "{}" {
938 | continue
939 | }
940 |
941 | if string(j) == "null" {
942 | continue
943 | }
944 |
945 | if j[0] != '{' {
946 | if len(result) == 1 && (isObject || bytes.Equal(result, j)) {
947 | result = j
948 | isObject = false
949 |
950 | continue
951 | }
952 |
953 | return nil, errors.New("failed to union map: object expected, " + string(j) + " received")
954 | }
955 |
956 | if !isObject {
957 | return nil, errors.New("failed to union " + string(result) + " and " + string(j))
958 | }
959 |
960 | if len(result) > 1 {
961 | result[len(result)-1] = ','
962 | }
963 |
964 | result = append(result, j[1:]...)
965 | }
966 |
967 | // Close empty result.
968 | if isObject && len(result) == 1 {
969 | result = append(result, '}')
970 | }
971 |
972 | return result, nil
973 | }
974 |
--------------------------------------------------------------------------------
/entities_extra_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | "github.com/swaggest/assertjson"
10 | "github.com/swaggest/jsonschema-go"
11 | "github.com/yudai/gojsondiff/formatter"
12 | )
13 |
14 | func TestSchema_MarshalJSON_roundtrip_draft7(t *testing.T) {
15 | data, err := ioutil.ReadFile("./resources/schema/draft-07.json")
16 | require.NoError(t, err)
17 |
18 | s := jsonschema.SchemaOrBool{}
19 | require.NoError(t, json.Unmarshal(data, &s))
20 |
21 | marshaled, err := json.Marshal(s)
22 | require.NoError(t, err)
23 | assertjson.Comparer{
24 | FormatterConfig: formatter.AsciiFormatterConfig{
25 | Coloring: true,
26 | },
27 | }.Equal(t, data, marshaled)
28 | }
29 |
30 | func BenchmarkSchema_UnmarshalJSON_raw(b *testing.B) {
31 | data, err := ioutil.ReadFile("./resources/schema/draft-07.json")
32 | require.NoError(b, err)
33 | b.ReportAllocs()
34 | b.ResetTimer()
35 |
36 | var s interface{}
37 |
38 | for i := 0; i < b.N; i++ {
39 | err = json.Unmarshal(data, &s)
40 | require.NoError(b, err)
41 | }
42 | }
43 |
44 | func BenchmarkSchema_UnmarshalJSON(b *testing.B) {
45 | data, err := ioutil.ReadFile("./resources/schema/draft-07.json")
46 | require.NoError(b, err)
47 | b.ReportAllocs()
48 | b.ResetTimer()
49 |
50 | s := jsonschema.SchemaOrBool{}
51 |
52 | for i := 0; i < b.N; i++ {
53 | err = json.Unmarshal(data, &s)
54 | require.NoError(b, err)
55 | }
56 | }
57 |
58 | func BenchmarkSchema_MarshalJSON_raw(b *testing.B) {
59 | data, err := ioutil.ReadFile("./resources/schema/draft-07.json")
60 | require.NoError(b, err)
61 |
62 | var s interface{}
63 |
64 | require.NoError(b, json.Unmarshal(data, &s))
65 |
66 | b.ReportAllocs()
67 | b.ResetTimer()
68 |
69 | for i := 0; i < b.N; i++ {
70 | _, err = json.Marshal(&s)
71 | require.NoError(b, err)
72 | }
73 | }
74 |
75 | func BenchmarkSchema_MarshalJSON(b *testing.B) {
76 | data, err := ioutil.ReadFile("./resources/schema/draft-07.json")
77 | require.NoError(b, err)
78 |
79 | s := jsonschema.SchemaOrBool{}
80 | require.NoError(b, json.Unmarshal(data, &s))
81 |
82 | b.ReportAllocs()
83 | b.ResetTimer()
84 |
85 | for i := 0; i < b.N; i++ {
86 | _, err = json.Marshal(&s)
87 | require.NoError(b, err)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/entities_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by github.com/swaggest/json-cli v1.7.7, DO NOT EDIT.
2 |
3 | package jsonschema
4 |
5 | import (
6 | "encoding/json"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 | "github.com/swaggest/assertjson"
11 | )
12 |
13 | func TestSchema_MarshalJSON_roundtrip(t *testing.T) {
14 | var (
15 | jsonValue = []byte(`{"$id":"cbbfff","$schema":"cdef","$ref":"aadce","$comment":"dc","title":"daecfe","description":"cddbd","readOnly":true,"examples":["accdf"],"multipleOf":9682.647,"maximum":8027.772,"exclusiveMaximum":3134.928,"minimum":928.915,"exclusiveMinimum":6923.534,"maxLength":5182,"minLength":8764,"pattern":"adeadb","additionalItems":{"$id":"cda","$schema":"bdc","$ref":"dbfee","$comment":"fcceaf","title":"fedacd","description":"ab","readOnly":true,"examples":["edc"],"multipleOf":4692.916,"maximum":9086.046,"exclusiveMaximum":962.626,"minimum":2936.848,"exclusiveMinimum":9033.798,"maxLength":2878,"minLength":1195,"pattern":"bffd","additionalItems":{"$id":"eae","$schema":"db","$ref":"bfdbc","$comment":"ec","title":"eb","description":"fffbce","readOnly":true,"examples":["dca"],"multipleOf":1400.521,"maximum":5149.376,"exclusiveMaximum":7924.243,"minimum":9446.003,"exclusiveMinimum":298.984,"maxLength":5866,"minLength":8831,"pattern":"bcf","items":{"$id":"fa","$schema":"abdfad","$ref":"aeab","$comment":"bafd","title":"eaceae","description":"cc","readOnly":true,"examples":["eadf"],"multipleOf":738.846,"maximum":1148.345,"exclusiveMaximum":7869.728,"minimum":9494.943,"exclusiveMinimum":641.609,"maxLength":4499,"minLength":3553,"pattern":"bcacc","additionalItems":{"$id":"fcadff","$schema":"eca","$ref":"bdb","$comment":"dfe","title":"cfdcf","description":"baf","readOnly":true,"examples":["dda"],"multipleOf":1920.369,"maximum":6968.485,"exclusiveMaximum":4643.651,"minimum":660.936,"exclusiveMinimum":2304.398,"maxLength":7555,"minLength":5082,"pattern":"de","items":[{"$id":"befdf","$schema":"dcba","$ref":"bffbb","$comment":"cdf","title":"caf","description":"eeacab","readOnly":true,"examples":["bd"],"multipleOf":2330.51,"maximum":7174.199,"exclusiveMaximum":8071.245,"minimum":6129.868,"exclusiveMinimum":3879.219,"maxLength":4269,"minLength":8635,"pattern":"cfcbcf","additionalItems":{"$id":"ce","$schema":"bb","$ref":"eabafb","$comment":"ab","title":"daac","description":"cba","readOnly":true,"examples":["df"],"multipleOf":1955.099,"maximum":9387.178,"exclusiveMaximum":5814.008,"minimum":3711.968,"exclusiveMinimum":9700.23,"maxLength":500,"minLength":8468,"pattern":"efc","items":{"ec":"babfe"},"bffb":"bdea"},"aaead":"ac"}],"abdb":"cccf"},"bed":"cec"},"efecaf":"fdeeb"},"afcfaf":"ec"},"items":{"$id":"eaeeaf","$schema":"ccb","$ref":"fdcbaf","$comment":"abcdaf","title":"ac","description":"dcefd","readOnly":true,"examples":["dfecaf"],"multipleOf":4595.993,"maximum":9479.754,"exclusiveMaximum":9613.211,"minimum":8271.328,"exclusiveMinimum":3342.518,"maxLength":150,"minLength":4721,"pattern":"dbc","additionalItems":{"$id":"fbd","$schema":"becee","$ref":"bceeab","$comment":"efefda","title":"ad","description":"dc","readOnly":true,"examples":["aadabb"],"multipleOf":2353.448,"maximum":8928.994,"exclusiveMaximum":8151.185,"minimum":5778.994,"exclusiveMinimum":5439.428,"maxLength":1840,"minLength":1913,"pattern":"eccf","items":{"$id":"eeacfa","$schema":"ffd","$ref":"cd","$comment":"afa","title":"fcac","description":"ddd","readOnly":true,"examples":["aceba"],"multipleOf":6980.874,"maximum":9005.385,"exclusiveMaximum":8644.93,"minimum":47.628,"exclusiveMinimum":3227.487,"maxLength":2642,"minLength":6708,"pattern":"bcacab","additionalItems":{"$id":"ae","$schema":"feb","$ref":"adfd","$comment":"efe","title":"ec","description":"dff","readOnly":true,"examples":["facf"],"multipleOf":2524.415,"maximum":2988.703,"exclusiveMaximum":8549.125,"minimum":5407.655,"exclusiveMinimum":5277.619,"maxLength":8599,"minLength":8022,"pattern":"eda","maxItems":443,"minItems":5190,"uniqueItems":true,"contains":{"$id":"cafacf","$schema":"eddbb","$ref":"aeccac","$comment":"cbe","title":"ef","description":"befe","readOnly":true,"examples":["dbfbf"],"multipleOf":1393.661,"maximum":9163.139,"exclusiveMaximum":5403.505,"minimum":9096.446,"exclusiveMinimum":4671.386,"maxLength":2571,"minLength":2187,"pattern":"cffaee","additionalItems":{"$id":"dcddce","$schema":"ebceba","$ref":"ce","$comment":"cdbcc","title":"ecabba","description":"ccfc","readOnly":true,"examples":["dfce"],"multipleOf":3930.01,"maximum":8729.351,"exclusiveMaximum":808.757,"minimum":8447.251,"exclusiveMinimum":3521.861,"maxLength":5384,"minLength":10,"pattern":"abcce","items":{"$id":"caee","$schema":"cbed","$ref":"edbe","$comment":"bddcd","title":"eb","description":"bbab","readOnly":true,"examples":["ddf"],"multipleOf":4461.549,"maximum":3003.183,"exclusiveMaximum":9680.159,"minimum":1430.915,"exclusiveMinimum":3596.243,"maxLength":9013,"pattern":"be","additionalItems":{"becfee":"aaab"},"fe":"da"},"ce":"bfcfa"},"af":"bfe"},"cf":"accb"},"aa":"dfba"},"ab":"ac"},"faceb":"cffd"},"maxItems":7876,"minItems":5111,"uniqueItems":true,"contains":{"$id":"effa","$schema":"bfd","$ref":"dde","$comment":"ecf","title":"fbffc","description":"ffde","readOnly":true,"examples":["df"],"multipleOf":7662.348,"maximum":7357.585,"exclusiveMaximum":3425.199,"minimum":9096.46,"exclusiveMinimum":8458.669,"maxLength":2774,"minLength":4288,"pattern":"caf","additionalItems":{"$id":"bdeeae","$schema":"faabea","$ref":"befcaa","$comment":"bf","title":"aacdcd","description":"bdc","readOnly":true,"examples":["ebaebb"],"multipleOf":6881.473,"maximum":104.632,"exclusiveMaximum":6088.522,"minimum":5386.734,"exclusiveMinimum":1063.007,"maxLength":30,"minLength":2807,"pattern":"cfeb","items":{"$id":"bfddfd","$schema":"ea","$ref":"ba","$comment":"dbebb","title":"aac","description":"cfd","readOnly":true,"examples":["ad"],"multipleOf":8745.228,"maximum":1468.777,"exclusiveMaximum":8478.052,"minimum":2643.71,"exclusiveMinimum":6477.591,"maxLength":8191,"minLength":5856,"pattern":"abaceb","additionalItems":{"$id":"ed","$schema":"bdfeda","$ref":"ef","$comment":"dd","title":"ab","description":"fdecf","readOnly":true,"examples":["adbbf"],"multipleOf":4529.082,"maximum":8803.554,"exclusiveMaximum":6283.462,"minimum":4980.335,"exclusiveMinimum":2901.678,"maxLength":3334,"minLength":93,"pattern":"bbbeea","items":[{"$id":"ec","$schema":"bada","$ref":"dbe","$comment":"cceec","title":"aa","description":"ceadd","readOnly":true,"examples":["bcdde"],"multipleOf":4394.605,"maximum":7019.063,"exclusiveMaximum":1605.003,"minimum":2404.316,"exclusiveMinimum":8792.838,"maxLength":5742,"minLength":4785,"pattern":"efaafd","additionalItems":{"$id":"ccfe","$schema":"cca","$ref":"ea","$comment":"eceb","title":"dcbef","description":"afab","readOnly":true,"examples":["cefedf"],"multipleOf":9727.903,"maximum":4158.505,"exclusiveMaximum":7674.981,"minimum":1959.72,"exclusiveMinimum":8242.554,"maxLength":1660,"minLength":6326,"pattern":"caefa","items":{"ebad":"edddff"},"bb":"dcdec"},"abcc":"cc"}],"de":"cfdca"},"cddfdf":"accefd"},"ecdb":"bb"},"ccccf":"ada"},"maxProperties":4581,"minProperties":6737,"required":["ea"],"additionalProperties":{"$id":"eabbff","$schema":"efccf","$ref":"cbbbd","$comment":"ccdfa","title":"fb","description":"dbbff","readOnly":true,"examples":["afbeaf"],"multipleOf":4933.189,"maximum":3771.449,"exclusiveMaximum":7339.696,"minimum":267.252,"exclusiveMinimum":1848.369,"maxLength":1109,"minLength":9207,"pattern":"ebbac","additionalItems":{"$id":"faf","$schema":"beda","$ref":"fdc","$comment":"bdbb","title":"bebdb","description":"cdaef","readOnly":true,"examples":["dc"],"multipleOf":4464.657,"maximum":1468.368,"exclusiveMaximum":3288.864,"minimum":7293.46,"exclusiveMinimum":3441.308,"maxLength":1887,"minLength":475,"pattern":"dfcbd","items":{"$id":"aaa","$schema":"bbbb","$ref":"aebbfb","$comment":"efbafb","title":"befffe","description":"decac","readOnly":true,"examples":["fbd"],"multipleOf":2065.334,"maximum":7205.459,"exclusiveMaximum":762.812,"minimum":8473.593,"exclusiveMinimum":5079.709,"maxLength":5686,"minLength":2615,"pattern":"afecef","additionalItems":{"$id":"bc","$schema":"dfcd","$ref":"bcfbd","$comment":"fbcadd","title":"affee","description":"afdfca","readOnly":true,"examples":["bcfef"],"multipleOf":7587.711,"maximum":2088.699,"exclusiveMaximum":7169.573,"minimum":7444.97,"exclusiveMinimum":9873.274,"maxLength":9777,"minLength":2781,"pattern":"bf","items":[{"$id":"ccdf","$schema":"eda","$ref":"cdcfdf","$comment":"ba","title":"beebc","description":"aa","readOnly":true,"examples":["acfde"],"multipleOf":2203.62,"maximum":3565.182,"exclusiveMaximum":3021.665,"minimum":9099.925,"exclusiveMinimum":8830.402,"maxLength":1787,"minLength":5432,"pattern":"daa","additionalItems":{"$id":"addbf","$schema":"afeed","$ref":"ef","$comment":"aabff","title":"cbcbf","description":"aabca","readOnly":true,"examples":["edbacb"],"multipleOf":1313.008,"maximum":9093.211,"exclusiveMaximum":1605.113,"minimum":6055.185,"exclusiveMinimum":8700.947,"maxLength":7668,"minLength":9871,"pattern":"edc","items":{"adecb":"cdbc"},"fff":"fedcff"},"abcbdd":"aedc"}],"fafe":"bfabe"},"cfefbb":"ea"},"ef":"bc"},"accfeb":"ecb"},"definitions":{"abf":{"$id":"eba","$schema":"ec","$ref":"daf","$comment":"fececc","title":"ad","description":"faccdc","readOnly":true,"examples":["fbdeaf"],"multipleOf":968.643,"maximum":3203.873,"exclusiveMaximum":1464.101,"minimum":9824.927,"exclusiveMinimum":4151.62,"maxLength":6363,"minLength":5249,"pattern":"debf","additionalItems":{"$id":"ee","$schema":"efcae","$ref":"aa","$comment":"eedbbd","title":"ebfac","description":"afcccc","readOnly":true,"examples":["dcd"],"multipleOf":4772.999,"maximum":2790.63,"exclusiveMaximum":4498.865,"minimum":6088.703,"exclusiveMinimum":9358.699,"maxLength":6825,"minLength":3901,"pattern":"ee","items":{"$id":"bcab","$schema":"ad","$ref":"de","$comment":"fdddef","title":"cab","description":"ad","readOnly":true,"examples":["bbf"],"multipleOf":3719.849,"maximum":1632.602,"exclusiveMaximum":3553.724,"minimum":6381.138,"exclusiveMinimum":577.49,"maxLength":2362,"minLength":6316,"pattern":"dbddbf","additionalItems":{"$id":"deebeb","$schema":"ffc","$ref":"fcf","$comment":"cdffb","title":"aeabef","description":"bc","readOnly":true,"examples":["fdfa"],"multipleOf":8596.65,"maximum":8611.056,"exclusiveMaximum":6716.312,"minimum":8137.529,"exclusiveMinimum":6335.107,"maxLength":7212,"minLength":1248,"pattern":"dba","items":[{"$id":"dace","$schema":"ffe","$ref":"aab","$comment":"caa","title":"ceeffd","description":"bc","readOnly":true,"examples":["bbaeb"],"multipleOf":4228.104,"maximum":7519.105,"exclusiveMaximum":1075.981,"minimum":1014.964,"exclusiveMinimum":6237.08,"maxLength":5849,"minLength":480,"pattern":"beaeaa","additionalItems":{"$id":"abc","$schema":"bc","$ref":"fe","$comment":"efb","title":"fcffcd","description":"db","readOnly":true,"examples":["aca"],"multipleOf":4698.421,"maximum":5997.322,"exclusiveMaximum":9794.854,"minimum":4118.917,"exclusiveMinimum":3644.416,"maxLength":7378,"pattern":"af","items":true,"maxItems":9209,"uniqueItems":true,"contains":{"dbeea":"efbada"},"cfecef":"fdea"},"bcaceb":"bbed"}],"bef":"ccff"},"fbedff":"bdfa"},"ccb":"afadf"},"dff":"cafe"}},"properties":{"faa":{"$id":"da","$schema":"faeff","$ref":"bcfcec","$comment":"fec","title":"ceb","description":"cf","readOnly":true,"examples":["cabe"],"multipleOf":3426.232,"maximum":1138.85,"exclusiveMaximum":4171.074,"minimum":6518.241,"exclusiveMinimum":7608.632,"maxLength":4027,"minLength":8625,"pattern":"eebe","additionalItems":{"$id":"dfddca","$schema":"eebb","$ref":"eccc","$comment":"eb","title":"bb","description":"fab","readOnly":true,"examples":["faaf"],"multipleOf":7405.04,"maximum":5476.095,"exclusiveMaximum":3179.993,"minimum":2202.067,"exclusiveMinimum":4956.045,"maxLength":6714,"minLength":8555,"pattern":"cdac","items":{"$id":"cfac","$schema":"bfe","$ref":"bfaea","$comment":"affeea","title":"bc","description":"ebf","readOnly":true,"examples":["ad"],"multipleOf":9675.203,"maximum":7744.998,"exclusiveMaximum":322.924,"minimum":3258.774,"exclusiveMinimum":4786.324,"maxLength":8898,"minLength":813,"pattern":"edaddd","additionalItems":{"$id":"bdab","$schema":"cdadb","$ref":"ecbacf","$comment":"caeae","title":"ce","description":"de","readOnly":true,"examples":["eaca"],"multipleOf":8477.157,"maximum":8713.779,"exclusiveMaximum":4541.136,"minimum":8820.957,"exclusiveMinimum":155.02,"maxLength":4297,"minLength":8731,"pattern":"cbdead","items":[{"$id":"bbdb","$schema":"bf","$ref":"ebdf","$comment":"ed","title":"bdfeb","description":"cfba","readOnly":true,"examples":["dacee"],"multipleOf":7056.506,"maximum":3449.269,"exclusiveMaximum":287.194,"minimum":8978.609,"exclusiveMinimum":4085.849,"maxLength":4346,"minLength":9311,"pattern":"bc","additionalItems":{"$id":"efb","$schema":"aed","$ref":"ffd","$comment":"cf","title":"ec","description":"fbfee","readOnly":true,"examples":["eaca"],"multipleOf":2483.659,"maximum":5295.014,"exclusiveMaximum":2951.491,"minimum":9514.692,"exclusiveMinimum":8726.989,"maxLength":3125,"pattern":"ebc","items":true,"maxItems":8970,"uniqueItems":true,"contains":{"facff":"dbb"},"bdce":"fbf"},"ca":"ed"}],"dbcc":"cba"},"dcbe":"cdeec"},"dcdfa":"cdeac"},"ceb":"caeed"}},"patternProperties":{"fca":{"$id":"cbfed","$schema":"bcaaf","$ref":"bcef","$comment":"bfab","title":"bacfe","description":"eabc","readOnly":true,"examples":["ebfc"],"multipleOf":5071.687,"maximum":8533.244,"exclusiveMaximum":4547.468,"minimum":7213.709,"exclusiveMinimum":5879.127,"maxLength":9310,"minLength":2641,"pattern":"cfbb","additionalItems":{"$id":"dafcaf","$schema":"eefe","$ref":"aafddd","$comment":"bf","title":"bdaaec","description":"eaadfb","readOnly":true,"examples":["cafff"],"multipleOf":2338.453,"maximum":7988.736,"exclusiveMaximum":1767.612,"minimum":6137.632,"exclusiveMinimum":3911.6,"maxLength":7262,"minLength":3206,"pattern":"ae","items":{"$id":"ea","$schema":"bfbfd","$ref":"fbbd","$comment":"edfbbd","title":"ee","description":"bbcb","readOnly":true,"examples":["bfecf"],"multipleOf":8009.204,"maximum":3304.587,"exclusiveMaximum":5466.654,"minimum":8435.098,"exclusiveMinimum":1816.913,"maxLength":4284,"minLength":7008,"pattern":"bdccfc","additionalItems":{"$id":"deeca","$schema":"fafe","$ref":"aaf","$comment":"ae","title":"cabb","description":"aafcb","readOnly":true,"examples":["deed"],"multipleOf":530.52,"maximum":8218.672,"exclusiveMaximum":8390.621,"minimum":6282.768,"exclusiveMinimum":3665.756,"maxLength":9563,"minLength":9223,"pattern":"babb","items":[{"$id":"eaefb","$schema":"bc","$ref":"baaf","$comment":"cdff","title":"ea","description":"be","readOnly":true,"examples":["cf"],"multipleOf":1924.3,"maximum":3949.428,"exclusiveMaximum":1052.784,"minimum":4616.336,"exclusiveMinimum":963.585,"maxLength":5609,"minLength":4369,"pattern":"aefff","additionalItems":{"$id":"cc","$schema":"aea","$ref":"ebada","$comment":"cab","title":"afbbc","description":"deecba","readOnly":true,"examples":["cabd"],"multipleOf":1294.715,"maximum":8731.071,"exclusiveMaximum":7809.456,"minimum":7003.194,"exclusiveMinimum":2616.332,"maxLength":4705,"pattern":"fdea","items":true,"maxItems":3635,"uniqueItems":true,"contains":{"edfdc":"dfd"},"fbfad":"fcbee"},"eafbaa":"ddc"}],"efc":"fcfa"},"ebb":"dfca"},"dffab":"bc"},"fc":"af"}},"dependencies":{"eefed":{"$id":"ece","$schema":"cedfbd","$ref":"aabdec","$comment":"df","title":"cbebf","description":"dafa","readOnly":true,"examples":["adedfc"],"multipleOf":3930.337,"maximum":9490.775,"exclusiveMaximum":7906.579,"minimum":9523.513,"exclusiveMinimum":6451.139,"maxLength":1071,"minLength":3939,"pattern":"ecd","additionalItems":{"$id":"aeddba","$schema":"ebfd","$ref":"ad","$comment":"fe","title":"cedb","description":"ecbda","readOnly":true,"examples":["da"],"multipleOf":3638.036,"maximum":2364.605,"exclusiveMaximum":6575.577,"minimum":6606.323,"exclusiveMinimum":4533.265,"maxLength":896,"minLength":7691,"pattern":"cbecc","items":{"$id":"ea","$schema":"fdcade","$ref":"bebfbb","$comment":"cccdd","title":"cbabc","description":"afbefe","readOnly":true,"examples":["eeccb"],"multipleOf":2772.724,"maximum":2653.558,"exclusiveMaximum":5348.788,"minimum":1100.658,"exclusiveMinimum":7679.861,"maxLength":7113,"minLength":531,"pattern":"bafaae","additionalItems":{"$id":"ffdcfb","$schema":"eecee","$ref":"bbccef","$comment":"fabfcb","title":"cf","description":"fbae","readOnly":true,"examples":["ccac"],"multipleOf":3537.124,"maximum":4924.12,"exclusiveMaximum":2539.135,"minimum":667.921,"exclusiveMinimum":9110.269,"maxLength":5970,"minLength":7256,"pattern":"fec","maxItems":345,"minItems":2813,"uniqueItems":true,"contains":{"$id":"ddbe","$schema":"cbc","$ref":"eb","$comment":"cbea","title":"cfe","description":"aedcf","readOnly":true,"examples":["eabcab"],"multipleOf":7397.659,"maximum":9904.072,"exclusiveMaximum":5437.349,"minimum":7739.389,"exclusiveMinimum":7314.642,"maxLength":3343,"minLength":6023,"pattern":"bfeada","additionalItems":{"$id":"deaba","$schema":"db","$ref":"ee","$comment":"bbefdc","title":"ecbbff","description":"bbcaf","readOnly":true,"examples":["bbcda"],"multipleOf":4218.909,"maximum":7580.859,"exclusiveMaximum":4385.947,"minimum":8519.606,"exclusiveMinimum":8014.083,"maxLength":8442,"minLength":157,"pattern":"ed","items":{"eee":"ca"},"eaa":"cbede"},"edde":"daea"},"bfcfc":"facbd"},"bd":"fdceb"},"ea":"ab"},"ddc":"dbb"}},"propertyNames":{"$id":"cfccc","$schema":"fdef","$ref":"fafa","$comment":"ebfcf","title":"bdfbb","description":"ccefc","readOnly":true,"examples":["ccabb"],"multipleOf":3563.046,"maximum":9357.596,"exclusiveMaximum":5051.997,"minimum":2836.162,"exclusiveMinimum":4028.073,"maxLength":2746,"minLength":8544,"pattern":"aae","additionalItems":{"$id":"cdeb","$schema":"cb","$ref":"dbc","$comment":"aacb","title":"ce","description":"ceeff","readOnly":true,"examples":["affaac"],"multipleOf":9377.255,"maximum":3914.912,"exclusiveMaximum":9899.648,"minimum":2402.482,"exclusiveMinimum":9820.002,"maxLength":6864,"minLength":5850,"pattern":"fab","items":{"$id":"cabca","$schema":"bf","$ref":"dbedae","$comment":"ce","title":"abba","description":"eefac","readOnly":true,"examples":["fcddb"],"multipleOf":3217.16,"maximum":3182.862,"exclusiveMaximum":7802.243,"minimum":685.972,"exclusiveMinimum":1458.709,"maxLength":3531,"minLength":2449,"pattern":"dbaef","additionalItems":{"$id":"eb","$schema":"cfdd","$ref":"fe","$comment":"eacb","title":"febab","description":"eec","readOnly":true,"examples":["acaab"],"multipleOf":3346.537,"maximum":4538.846,"exclusiveMaximum":8531.366,"minimum":5578.695,"exclusiveMinimum":8855.367,"maxLength":5061,"minLength":1814,"pattern":"cbbffe","items":[{"$id":"abcebc","$schema":"eebdc","$ref":"deceb","$comment":"dd","title":"dcadb","description":"fbbc","readOnly":true,"examples":["ceccf"],"multipleOf":4097.903,"maximum":9599.869,"exclusiveMaximum":873.438,"minimum":5164.115,"exclusiveMinimum":3711.268,"maxLength":6428,"minLength":6721,"pattern":"ede","additionalItems":{"$id":"cfa","$schema":"fdac","$ref":"fdfafe","$comment":"fefebb","title":"addc","description":"bbccf","readOnly":true,"examples":["faca"],"multipleOf":3644.29,"maximum":8680.533,"exclusiveMaximum":8922.847,"minimum":5411.264,"exclusiveMinimum":278.09,"maxLength":7982,"minLength":854,"pattern":"aef","items":{"edfca":"edcf"},"efbf":"ecc"},"ba":"bfbed"}],"df":"af"},"decd":"dbba"},"add":"ebe"},"dfaafa":"eebe"},"enum":["adbe"],"type":"array","format":"feaa","contentMediaType":"efee","contentEncoding":"aebc","if":{"$id":"ad","$schema":"fdbed","$ref":"ddad","$comment":"ebef","title":"feb","description":"ec","readOnly":true,"examples":["ac"],"multipleOf":7511.147,"maximum":4628.785,"exclusiveMaximum":2118.002,"minimum":5325.378,"exclusiveMinimum":8301.735,"maxLength":8259,"minLength":2401,"pattern":"bfbde","additionalItems":{"$id":"bb","$schema":"edbefe","$ref":"ed","$comment":"fbdffc","title":"fbcfeb","description":"edeccc","readOnly":true,"examples":["daccfa"],"multipleOf":8106.48,"maximum":5153.686,"exclusiveMaximum":8600.126,"minimum":452.093,"exclusiveMinimum":4586.05,"maxLength":119,"minLength":9306,"pattern":"db","items":{"$id":"cb","$schema":"cf","$ref":"bfeba","$comment":"abf","title":"fafde","description":"fbeaad","readOnly":true,"examples":["bfae"],"multipleOf":5154.422,"maximum":5192.47,"exclusiveMaximum":4000.059,"minimum":2402.211,"exclusiveMinimum":9862.482,"maxLength":3859,"minLength":282,"pattern":"ffae","additionalItems":{"$id":"ceec","$schema":"ce","$ref":"cddb","$comment":"fdcba","title":"ba","description":"be","readOnly":true,"examples":["cec"],"multipleOf":4107.622,"maximum":416.701,"exclusiveMaximum":2240.753,"minimum":9897.08,"exclusiveMinimum":7230.852,"maxLength":4649,"minLength":5963,"pattern":"eedbbb","items":[{"$id":"bbbc","$schema":"cfbadf","$ref":"adfbd","$comment":"eafe","title":"ddfae","description":"cdadb","readOnly":true,"examples":["eedeaa"],"multipleOf":2507.821,"maximum":2206.72,"exclusiveMaximum":4758.479,"minimum":439.171,"exclusiveMinimum":4731.603,"maxLength":7132,"minLength":1690,"pattern":"efffd","additionalItems":{"$id":"aff","$schema":"ffbfbe","$ref":"bf","$comment":"cdaeaf","title":"cbedeb","description":"ed","readOnly":true,"examples":["dbdafc"],"multipleOf":8676.404,"maximum":8255.458,"exclusiveMaximum":8498.869,"minimum":181.05,"exclusiveMinimum":8084.305,"maxLength":5731,"minLength":1307,"pattern":"eccd","items":{"ffb":"ed"},"fda":"ccfdd"},"dbbccd":"ad"}],"ddadff":"abff"},"eeced":"bb"},"baaad":"fdbdf"},"ffb":"bbce"},"then":{"$id":"fb","$schema":"fb","$ref":"adb","$comment":"ac","title":"cecab","description":"afeef","readOnly":true,"examples":["adfae"],"multipleOf":7029.614,"maximum":235.932,"exclusiveMaximum":1850.165,"minimum":5306.341,"exclusiveMinimum":5657.756,"maxLength":3771,"minLength":9088,"pattern":"bed","additionalItems":{"$id":"badaa","$schema":"eb","$ref":"cddfb","$comment":"fdc","title":"ad","description":"fd","readOnly":true,"examples":["dcc"],"multipleOf":4663.624,"maximum":777.466,"exclusiveMaximum":4910.594,"minimum":2089.227,"exclusiveMinimum":7668.857,"maxLength":8392,"minLength":6343,"pattern":"ddfae","items":{"$id":"fcb","$schema":"caa","$ref":"bc","$comment":"dfcde","title":"aeaabe","description":"dc","readOnly":true,"examples":["bcedff"],"multipleOf":1937.652,"maximum":146.673,"exclusiveMaximum":772.081,"minimum":2152.881,"exclusiveMinimum":7565.477,"maxLength":1174,"minLength":5641,"pattern":"bcbdd","additionalItems":{"$id":"dea","$schema":"bcf","$ref":"bbe","$comment":"ddd","title":"cfdbcb","description":"afed","readOnly":true,"examples":["aaddc"],"multipleOf":754.033,"maximum":2189.55,"exclusiveMaximum":4257.818,"minimum":3308.452,"exclusiveMinimum":6020.476,"maxLength":3179,"minLength":4254,"pattern":"fdb","items":[{"$id":"cfda","$schema":"ae","$ref":"cccbcb","$comment":"dfcfc","title":"eb","description":"eeba","readOnly":true,"examples":["fcff"],"multipleOf":163.951,"maximum":6194.302,"exclusiveMaximum":2208.965,"minimum":9807.01,"exclusiveMinimum":5115.111,"maxLength":6435,"minLength":7883,"pattern":"dbbcde","additionalItems":{"$id":"cbee","$schema":"ccd","$ref":"edddc","$comment":"abdc","title":"cef","description":"fffa","readOnly":true,"examples":["fcfbd"],"multipleOf":8167.723,"maximum":4336.992,"exclusiveMaximum":8778.136,"minimum":444.218,"exclusiveMinimum":5710.517,"maxLength":6953,"minLength":9640,"pattern":"afbe","items":{"fadcee":"dcbfe"},"bbdebf":"ffeef"},"fbea":"aebcf"}],"fde":"eba"},"aadb":"fcfa"},"eea":"bde"},"dbfbd":"eddd"},"else":{"$id":"adcfab","$schema":"bbcda","$ref":"eb","$comment":"cd","title":"dcbd","description":"aaaca","readOnly":true,"examples":["ff"],"multipleOf":4979.965,"maximum":7419.9,"exclusiveMaximum":9700.117,"minimum":9828.825,"exclusiveMinimum":9811.716,"maxLength":4161,"minLength":3199,"pattern":"aac","additionalItems":{"$id":"ecfb","$schema":"fcd","$ref":"fefdbd","$comment":"ccffd","title":"afbd","description":"da","readOnly":true,"examples":["cab"],"multipleOf":726.083,"maximum":4190.405,"exclusiveMaximum":1727.838,"minimum":6722.745,"exclusiveMinimum":3224.252,"maxLength":7952,"minLength":2484,"pattern":"cbdcae","items":{"$id":"bfbf","$schema":"eaf","$ref":"cb","$comment":"cdfd","title":"bea","description":"bacf","readOnly":true,"examples":["ed"],"multipleOf":9255.547,"maximum":6763.89,"exclusiveMaximum":9280.021,"minimum":2317.8,"exclusiveMinimum":2179.355,"maxLength":4136,"minLength":6830,"pattern":"ceedf","additionalItems":{"$id":"bdcb","$schema":"bbdbb","$ref":"ff","$comment":"efceea","title":"cdcfac","description":"bebdb","readOnly":true,"examples":["cced"],"multipleOf":464.688,"maximum":4718.719,"exclusiveMaximum":6277.669,"minimum":9818.262,"exclusiveMinimum":6554.632,"maxLength":1329,"minLength":2560,"pattern":"cfdc","items":[{"$id":"cd","$schema":"dd","$ref":"cb","$comment":"ecfeb","title":"de","description":"ccac","readOnly":true,"examples":["acfcef"],"multipleOf":9481.089,"maximum":2777.85,"exclusiveMaximum":9892.559,"minimum":8272.176,"exclusiveMinimum":2411.349,"maxLength":3645,"minLength":9337,"pattern":"cee","additionalItems":{"$id":"ed","$schema":"afdbd","$ref":"dfcbb","$comment":"dae","title":"be","description":"bacbab","readOnly":true,"examples":["bbfaf"],"multipleOf":8788.026,"maximum":875.64,"exclusiveMaximum":4024.206,"minimum":3998.15,"exclusiveMinimum":2032.733,"maxLength":2796,"minLength":4064,"pattern":"ebcf","items":{"bcfdbc":"fba"},"adcfdb":"abf"},"eacae":"cdcbb"}],"daddac":"ebedcb"},"cbddde":"fefc"},"ecfb":"eabecc"},"dbb":"fd"},"allOf":[{"$id":"ddaa","$schema":"dfcc","$ref":"fa","$comment":"bc","title":"fdac","description":"db","readOnly":true,"examples":["bccdc"],"multipleOf":293.692,"maximum":6285.031,"exclusiveMaximum":5551.313,"minimum":6455.082,"exclusiveMinimum":2930.686,"maxLength":2494,"minLength":5775,"pattern":"cebc","additionalItems":{"$id":"bdf","$schema":"ea","$ref":"badfe","$comment":"edadda","title":"debbf","description":"accdcb","readOnly":true,"examples":["ebdfa"],"multipleOf":4446.366,"maximum":7311.645,"exclusiveMaximum":9567.352,"minimum":5455.166,"exclusiveMinimum":9321.21,"maxLength":6065,"minLength":4354,"pattern":"fdba","items":{"$id":"adffc","$schema":"feaf","$ref":"eb","$comment":"db","title":"cd","description":"cdcdef","readOnly":true,"examples":["dd"],"multipleOf":6789.959,"maximum":1564.413,"exclusiveMaximum":7240.122,"minimum":1939.455,"exclusiveMinimum":26.43,"maxLength":4951,"minLength":4077,"pattern":"aaea","additionalItems":{"$id":"af","$schema":"faafb","$ref":"cdbfab","$comment":"aad","title":"fc","description":"aeb","readOnly":true,"examples":["aecfe"],"multipleOf":3038.281,"maximum":8927.122,"exclusiveMaximum":5217.071,"minimum":8363.857,"exclusiveMinimum":7123.41,"maxLength":5265,"minLength":3053,"pattern":"ebea","items":[{"$id":"ade","$schema":"cbdbdf","$ref":"ebeebc","$comment":"aeea","title":"dfcbf","description":"dacfa","readOnly":true,"examples":["cccccd"],"multipleOf":8251.928,"maximum":703.668,"exclusiveMaximum":320.81,"minimum":5588.936,"exclusiveMinimum":8779.726,"maxLength":6752,"minLength":346,"pattern":"aed","additionalItems":{"$id":"ddef","$schema":"dac","$ref":"ddfed","$comment":"cef","title":"dbde","description":"abafb","readOnly":true,"examples":["ec"],"multipleOf":1913.529,"maximum":2146.768,"exclusiveMaximum":3871.075,"minimum":9712.17,"exclusiveMinimum":8540.744,"maxLength":2522,"pattern":"faf","items":true,"maxItems":6236,"uniqueItems":true,"contains":{"afbcef":"eda"},"ddfa":"cefaf"},"bcba":"bd"}],"ac":"aecbe"},"dacc":"bceddc"},"ee":"bfe"},"ffaebd":"bca"}],"anyOf":[{"$id":"fadcb","$schema":"feabcf","$ref":"cea","$comment":"bdabd","title":"fa","description":"bdfeb","readOnly":true,"examples":["bdacaa"],"multipleOf":7334.327,"maximum":6262.463,"exclusiveMaximum":7221.387,"minimum":976.729,"exclusiveMinimum":2771.143,"maxLength":2510,"minLength":4077,"pattern":"abbbed","additionalItems":{"$id":"ca","$schema":"bdd","$ref":"cbeec","$comment":"aefbe","title":"ef","description":"bfe","readOnly":true,"examples":["eb"],"multipleOf":6060.747,"maximum":8080.599,"exclusiveMaximum":261.573,"minimum":9572.734,"exclusiveMinimum":71.46,"maxLength":2596,"minLength":595,"pattern":"fbcb","items":{"$id":"eaaef","$schema":"feecd","$ref":"dbca","$comment":"cbee","title":"ba","description":"bdb","readOnly":true,"examples":["ea"],"multipleOf":1501.835,"maximum":4590.221,"exclusiveMaximum":2659.804,"minimum":9086.513,"exclusiveMinimum":6895.259,"maxLength":34,"minLength":151,"pattern":"ac","additionalItems":{"$id":"ce","$schema":"cac","$ref":"facac","$comment":"da","title":"fc","description":"dffb","readOnly":true,"examples":["fee"],"multipleOf":4845.216,"maximum":8645.436,"exclusiveMaximum":8874.663,"minimum":2810.56,"exclusiveMinimum":1375.298,"maxLength":9588,"minLength":9644,"pattern":"eca","items":[{"$id":"cebb","$schema":"bdaef","$ref":"caaeb","$comment":"aacd","title":"cdcaaa","description":"ee","readOnly":true,"examples":["ddbe"],"multipleOf":4123.377,"maximum":156.577,"exclusiveMaximum":6115.829,"minimum":6000.682,"exclusiveMinimum":5468.798,"maxLength":2517,"minLength":3893,"pattern":"addbb","additionalItems":{"$id":"dddce","$schema":"cca","$ref":"cedcf","$comment":"ba","title":"bf","description":"dfabeb","readOnly":true,"examples":["dcaea"],"multipleOf":4509.476,"maximum":9274.157,"exclusiveMaximum":3487.919,"minimum":7014.945,"exclusiveMinimum":3833.42,"maxLength":4125,"pattern":"fffbdd","items":true,"maxItems":6310,"uniqueItems":true,"contains":{"fcaec":"edaa"},"fab":"deaea"},"efccae":"ec"}],"ccac":"dfba"},"deafeb":"ed"},"ee":"ffdcaa"},"dfcaac":"fedb"}],"oneOf":[{"$id":"ecaaf","$schema":"edbffb","$ref":"acfcdf","$comment":"efcbfe","title":"feba","description":"eee","readOnly":true,"examples":["ebdad"],"multipleOf":7921.044,"maximum":5551.489,"exclusiveMaximum":3985.278,"minimum":9707.449,"exclusiveMinimum":9921.167,"maxLength":918,"minLength":598,"pattern":"add","additionalItems":{"$id":"aef","$schema":"faf","$ref":"bbaac","$comment":"bf","title":"de","description":"feebcf","readOnly":true,"examples":["fb"],"multipleOf":2772.924,"maximum":6934.009,"exclusiveMaximum":961.534,"minimum":4384.82,"exclusiveMinimum":1188.185,"maxLength":2187,"minLength":3179,"pattern":"efdbcc","items":{"$id":"fe","$schema":"cf","$ref":"ddcebb","$comment":"aab","title":"abae","description":"cdfdab","readOnly":true,"examples":["ebabf"],"multipleOf":2317.406,"maximum":8471.282,"exclusiveMaximum":9560.112,"minimum":5034.273,"exclusiveMinimum":915.799,"maxLength":5272,"minLength":775,"pattern":"cdfddf","additionalItems":{"$id":"affa","$schema":"ebaff","$ref":"cb","$comment":"fef","title":"dee","description":"fabac","readOnly":true,"examples":["ee"],"multipleOf":5941.849,"maximum":7937.101,"exclusiveMaximum":1253.694,"minimum":1766.055,"exclusiveMinimum":7853.904,"maxLength":931,"minLength":6200,"pattern":"db","items":[{"$id":"cdd","$schema":"fce","$ref":"fabeaa","$comment":"fd","title":"bdbdc","description":"bcd","readOnly":true,"examples":["ab"],"multipleOf":2498.53,"maximum":3839.759,"exclusiveMaximum":3052.711,"minimum":1890.437,"exclusiveMinimum":9424.467,"maxLength":4688,"minLength":6189,"pattern":"feef","additionalItems":{"$id":"adf","$schema":"dfecea","$ref":"fcebc","$comment":"aceafc","title":"aaed","description":"badff","readOnly":true,"examples":["badda"],"multipleOf":6639.797,"maximum":5644.571,"exclusiveMaximum":8519.694,"minimum":7847.015,"exclusiveMinimum":1903.031,"maxLength":6429,"pattern":"ddea","items":true,"maxItems":8919,"uniqueItems":true,"contains":{"db":"ad"},"fda":"eeb"},"ccefbc":"cba"}],"edeaa":"fccddc"},"fdcca":"abfdf"},"adedaf":"fcebcb"},"fe":"fcdc"}],"not":{"$id":"eea","$schema":"de","$ref":"edbfba","$comment":"aeebb","title":"dbc","description":"ba","readOnly":true,"examples":["bfefd"],"multipleOf":3850.021,"maximum":9929.821,"exclusiveMaximum":7684.81,"minimum":3242.289,"exclusiveMinimum":6185.427,"maxLength":354,"minLength":4918,"pattern":"ba","additionalItems":{"$id":"efcfe","$schema":"bddfe","$ref":"aadbcf","$comment":"ee","title":"ddcbc","description":"ad","readOnly":true,"examples":["febbde"],"multipleOf":6827.044,"maximum":6754.8,"exclusiveMaximum":9375.545,"minimum":7955.232,"exclusiveMinimum":3941.896,"maxLength":400,"minLength":2726,"pattern":"dbb","items":{"$id":"fc","$schema":"fdfc","$ref":"aeb","$comment":"eafb","title":"ddaff","description":"acf","readOnly":true,"examples":["defbfe"],"multipleOf":4536.297,"maximum":7719.146,"exclusiveMaximum":979.323,"minimum":4583.946,"exclusiveMinimum":3187.435,"maxLength":2922,"minLength":1206,"pattern":"dfbcbd","additionalItems":{"$id":"dcefdb","$schema":"dbcdaf","$ref":"aebe","$comment":"eeeece","title":"bcac","description":"bee","readOnly":true,"examples":["fec"],"multipleOf":9100.642,"maximum":7262.292,"exclusiveMaximum":9802.02,"minimum":890.502,"exclusiveMinimum":7581.167,"maxLength":3267,"minLength":5741,"pattern":"aaeae","items":[{"$id":"bdbcfb","$schema":"cad","$ref":"bb","$comment":"aaace","title":"cd","description":"bdea","readOnly":true,"examples":["afc"],"multipleOf":9712.862,"maximum":3451.281,"exclusiveMaximum":1650.558,"minimum":9804.067,"exclusiveMinimum":7272.627,"maxLength":6313,"minLength":8907,"pattern":"dcc","additionalItems":{"$id":"edd","$schema":"ba","$ref":"afdbc","$comment":"aef","title":"dfb","description":"debad","readOnly":true,"examples":["fdfbe"],"multipleOf":1296.829,"maximum":1523.861,"exclusiveMaximum":7561.943,"minimum":4014.753,"exclusiveMinimum":7937.774,"maxLength":1972,"minLength":5849,"pattern":"ccc","items":{"cdcac":"ddf"},"efad":"bfdead"},"fdcdbd":"cef"}],"dbbcf":"ebefc"},"fbefbd":"bad"},"faff":"aef"},"fe":"debe"},"ffaeb":"cf"}`)
16 | v Schema
17 | )
18 |
19 | require.NoError(t, json.Unmarshal(jsonValue, &v))
20 |
21 | marshaled, err := json.Marshal(v)
22 | require.NoError(t, err)
23 | require.NoError(t, json.Unmarshal(marshaled, &v))
24 | assertjson.Equal(t, jsonValue, marshaled)
25 | }
26 |
27 | func TestSchemaOrBool_MarshalJSON_roundtrip(t *testing.T) {
28 | var (
29 | jsonValue = []byte(`{"$id":"fb","$schema":"eafab","$ref":"dcfea","$comment":"bbcb","title":"ed","description":"eadc","readOnly":true,"examples":["faad"],"multipleOf":839.87,"maximum":6022.654,"exclusiveMaximum":1086.478,"minimum":8214.408,"exclusiveMinimum":5293.03,"maxLength":347,"minLength":6601,"pattern":"dca","additionalItems":{"$id":"ebb","$schema":"efcbc","$ref":"ff","$comment":"eaadfb","title":"fbcbe","description":"efcf","readOnly":true,"examples":["ebdef"],"multipleOf":1901.305,"maximum":5830.016,"exclusiveMaximum":2961.337,"minimum":500.166,"exclusiveMinimum":63.096,"maxLength":9526,"minLength":4316,"pattern":"dbefb","additionalItems":{"$id":"adafc","$schema":"abeabb","$ref":"cdbb","$comment":"fbec","title":"bea","description":"cdcfdc","readOnly":true,"examples":["ffdf"],"multipleOf":1101.655,"maximum":7894.22,"exclusiveMaximum":740.115,"minimum":1918.185,"exclusiveMinimum":3197.588,"maxLength":3318,"minLength":6608,"pattern":"fcf","items":{"$id":"cfde","$schema":"afee","$ref":"aabbbd","$comment":"edb","title":"fdb","description":"fea","readOnly":true,"examples":["ccdbbe"],"multipleOf":405.274,"maximum":3149.975,"exclusiveMaximum":8668.097,"minimum":3669.226,"exclusiveMinimum":2370.132,"maxLength":2919,"minLength":3804,"pattern":"aaae","additionalItems":{"$id":"cf","$schema":"bbfcbe","$ref":"bafdb","$comment":"befbe","title":"da","description":"cacbcf","readOnly":true,"examples":["bc"],"multipleOf":5291.343,"maximum":3012.314,"exclusiveMaximum":7935.053,"minimum":1696.026,"exclusiveMinimum":359.756,"maxLength":8568,"minLength":1914,"pattern":"fafff","items":[{"$id":"ee","$schema":"bb","$ref":"acfeaf","$comment":"ecaf","title":"fefdae","description":"cc","readOnly":true,"examples":["ed"],"multipleOf":7762.282,"maximum":2534.579,"exclusiveMaximum":4916.08,"minimum":5235.021,"exclusiveMinimum":2044.664,"maxLength":8275,"minLength":7292,"pattern":"edc","additionalItems":{"$id":"afd","$schema":"ccfbbb","$ref":"cfdcfa","$comment":"adca","title":"edbdcb","description":"afac","readOnly":true,"examples":["bfaaf"],"multipleOf":3253.988,"maximum":9288.158,"exclusiveMaximum":8389.61,"minimum":8340.642,"exclusiveMinimum":629.177,"maxLength":8887,"minLength":1054,"pattern":"ae","items":{"aeff":"dbe"},"aadf":"cdc"},"dfaace":"fefcde"}],"dafee":"bfcefe"},"acea":"afcba"},"cbc":"afbcd"},"cabcea":"fca"},"bbee":"fa"}`)
30 | v SchemaOrBool
31 | )
32 |
33 | require.NoError(t, json.Unmarshal(jsonValue, &v))
34 |
35 | marshaled, err := json.Marshal(v)
36 | require.NoError(t, err)
37 | require.NoError(t, json.Unmarshal(marshaled, &v))
38 | assertjson.Equal(t, jsonValue, marshaled)
39 | }
40 |
41 | func TestItems_MarshalJSON_roundtrip(t *testing.T) {
42 | var (
43 | jsonValue = []byte(`{"$id":"ecaccf","$schema":"eae","$ref":"ea","$comment":"fff","title":"bb","description":"eebacd","readOnly":true,"examples":["dbfad"],"multipleOf":4917.775,"maximum":1175.302,"exclusiveMaximum":8524.027,"minimum":8839.227,"exclusiveMinimum":241.326,"maxLength":2756,"minLength":904,"pattern":"acab","additionalItems":{"$id":"eeb","$schema":"efb","$ref":"cd","$comment":"afffb","title":"ccc","description":"debdc","readOnly":true,"examples":["cae"],"multipleOf":634.542,"maximum":8815.53,"exclusiveMaximum":5843.11,"minimum":1096.463,"exclusiveMinimum":3357.815,"maxLength":182,"minLength":8818,"pattern":"bdbcfa","items":{"$id":"edcc","$schema":"dc","$ref":"bcd","$comment":"acdac","title":"ef","description":"dfb","readOnly":true,"examples":["acedfd"],"multipleOf":9892.658,"maximum":3759.378,"exclusiveMaximum":7804.789,"minimum":6946.61,"exclusiveMinimum":2279.031,"maxLength":9180,"minLength":307,"pattern":"cd","additionalItems":{"$id":"babdaa","$schema":"dced","$ref":"bafbba","$comment":"bfabe","title":"aadfe","description":"ecdeb","readOnly":true,"examples":["faefa"],"multipleOf":263.37,"maximum":6503.247,"exclusiveMaximum":3641.354,"minimum":6155.106,"exclusiveMinimum":4627.148,"maxLength":8139,"minLength":6086,"pattern":"cbac","items":[{"$id":"bc","$schema":"febdf","$ref":"ada","$comment":"cfa","title":"aacb","description":"aef","readOnly":true,"examples":["cce"],"multipleOf":8393.564,"maximum":3310.813,"exclusiveMaximum":2755.892,"minimum":6597.438,"exclusiveMinimum":106.683,"maxLength":4665,"minLength":4254,"pattern":"fcdf","additionalItems":{"$id":"ab","$schema":"cce","$ref":"ab","$comment":"dddea","title":"accbbd","description":"bda","readOnly":true,"examples":["be"],"multipleOf":4420.475,"maximum":8826.137,"exclusiveMaximum":5042.62,"minimum":6316.306,"exclusiveMinimum":1749.19,"maxLength":9637,"minLength":5231,"pattern":"adbf","items":{"fc":"bfefcd"},"ebfaa":"bad"},"abfddd":"dcc"}],"fd":"fabad"},"bba":"cfa"},"ff":"deab"},"ffafcc":"fe"}`)
44 | v Items
45 | )
46 |
47 | require.NoError(t, json.Unmarshal(jsonValue, &v))
48 |
49 | marshaled, err := json.Marshal(v)
50 | require.NoError(t, err)
51 | require.NoError(t, json.Unmarshal(marshaled, &v))
52 | assertjson.Equal(t, jsonValue, marshaled)
53 | }
54 |
55 | func TestDependenciesAdditionalProperties_MarshalJSON_roundtrip(t *testing.T) {
56 | var (
57 | jsonValue = []byte(`{"$id":"feff","$schema":"cebde","$ref":"ebaab","$comment":"fee","title":"acfcc","description":"ba","readOnly":true,"examples":["fa"],"multipleOf":937.049,"maximum":7372.905,"exclusiveMaximum":656.92,"minimum":7958.373,"exclusiveMinimum":8799.476,"maxLength":8269,"minLength":8214,"pattern":"abccf","additionalItems":{"$id":"bca","$schema":"fcaa","$ref":"eced","$comment":"caeedf","title":"bcebd","description":"eaeda","readOnly":true,"examples":["ca"],"multipleOf":8975.313,"maximum":2489.733,"exclusiveMaximum":1204.112,"minimum":7923.336,"exclusiveMinimum":5142.737,"maxLength":7192,"minLength":3209,"pattern":"ecbb","items":{"$id":"cebf","$schema":"ed","$ref":"bbae","$comment":"ea","title":"effe","description":"cfbcac","readOnly":true,"examples":["ddeda"],"multipleOf":7347.664,"maximum":9682.046,"exclusiveMaximum":2876.071,"minimum":2871.947,"exclusiveMinimum":3876.624,"maxLength":8030,"minLength":9187,"pattern":"bb","additionalItems":{"$id":"bc","$schema":"ddeeec","$ref":"cdddbb","$comment":"dbf","title":"cfafc","description":"afd","readOnly":true,"examples":["faaa"],"multipleOf":5919.355,"maximum":8817.027,"exclusiveMaximum":9939.18,"minimum":7761.035,"exclusiveMinimum":11.408,"maxLength":5713,"minLength":9096,"pattern":"bcdfd","items":[{"$id":"ecbe","$schema":"fad","$ref":"ff","$comment":"febd","title":"fead","description":"dd","readOnly":true,"examples":["edffc"],"multipleOf":1276.301,"maximum":1356.835,"exclusiveMaximum":6245.475,"minimum":1594.443,"exclusiveMinimum":8137.128,"maxLength":4453,"minLength":9905,"pattern":"feaeda","additionalItems":{"$id":"cf","$schema":"abbcdd","$ref":"aaeaaa","$comment":"bccdcc","title":"bfd","description":"fe","readOnly":true,"examples":["caf"],"multipleOf":1174.659,"maximum":3405.787,"exclusiveMaximum":5652.17,"minimum":6399.162,"exclusiveMinimum":7075.892,"maxLength":6218,"minLength":8880,"pattern":"ccaddb","items":{"baf":"eb"},"ecac":"ee"},"ccd":"cedbeb"}],"caeeca":"dcef"},"fbcaab":"dc"},"fdb":"afebc"},"dbb":"fb"}`)
58 | v DependenciesAdditionalProperties
59 | )
60 |
61 | require.NoError(t, json.Unmarshal(jsonValue, &v))
62 |
63 | marshaled, err := json.Marshal(v)
64 | require.NoError(t, err)
65 | require.NoError(t, json.Unmarshal(marshaled, &v))
66 | assertjson.Equal(t, jsonValue, marshaled)
67 | }
68 |
69 | func TestType_MarshalJSON_roundtrip(t *testing.T) {
70 | var (
71 | jsonValue = []byte(`"object"`)
72 | v Type
73 | )
74 |
75 | require.NoError(t, json.Unmarshal(jsonValue, &v))
76 |
77 | marshaled, err := json.Marshal(v)
78 | require.NoError(t, err)
79 | require.NoError(t, json.Unmarshal(marshaled, &v))
80 | assertjson.Equal(t, jsonValue, marshaled)
81 | }
82 |
83 | func TestSchemaWithWriteOnly_MarshalJSON_roundtrip(t *testing.T) {
84 | var (
85 | jsonValue = []byte(`{"$id":"someid","title":"title","description":"description","writeOnly":true}`)
86 | v Schema
87 | )
88 |
89 | require.NoError(t, json.Unmarshal(jsonValue, &v))
90 | marshaled, err := json.Marshal(v)
91 | require.NoError(t, err)
92 | require.NoError(t, json.Unmarshal(marshaled, &v))
93 | assertjson.Equal(t, jsonValue, marshaled)
94 | }
95 |
96 | func TestSchemaWithDeprecated_MarshalJSON_roundtrip(t *testing.T) {
97 | var (
98 | jsonValue = []byte(`{"$id":"someid","title":"title","description":"description","deprecated":true}`)
99 | v Schema
100 | )
101 |
102 | require.NoError(t, json.Unmarshal(jsonValue, &v))
103 | marshaled, err := json.Marshal(v)
104 | require.NoError(t, err)
105 | require.NoError(t, json.Unmarshal(marshaled, &v))
106 | assertjson.Equal(t, jsonValue, marshaled)
107 | }
108 |
--------------------------------------------------------------------------------
/example_enum_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/swaggest/assertjson"
7 | "github.com/swaggest/jsonschema-go"
8 | )
9 |
10 | type WeekDay string
11 |
12 | func (WeekDay) Enum() []interface{} {
13 | return []interface{}{
14 | "Monday",
15 | "Tuesday",
16 | "Wednesday",
17 | "Thursday",
18 | "Friday",
19 | "Saturday",
20 | "Sunday",
21 | }
22 | }
23 |
24 | type Shop struct {
25 | Days []WeekDay `json:"days,omitempty"` // This property uses dedicated named type to express enum.
26 | Days2 []string `json:"days2,omitempty"` // This property uses schema preparer to set up enum.
27 |
28 | // This scalar property uses field tag to set up enum.
29 | Day string `json:"day" enum:"Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday"`
30 | }
31 |
32 | var _ jsonschema.Preparer = Shop{}
33 |
34 | func (Shop) PrepareJSONSchema(schema *jsonschema.Schema) error {
35 | schema.Properties["days2"].TypeObject.WithEnum(
36 | "Monday",
37 | "Tuesday",
38 | "Wednesday",
39 | "Thursday",
40 | "Friday",
41 | "Saturday",
42 | "Sunday",
43 | )
44 |
45 | return nil
46 | }
47 |
48 | func ExampleEnum() {
49 | reflector := jsonschema.Reflector{}
50 |
51 | s, err := reflector.Reflect(Shop{}, jsonschema.StripDefinitionNamePrefix("JsonschemaGoTest"))
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | j, err := assertjson.MarshalIndentCompact(s, "", " ", 80)
57 | if err != nil {
58 | panic(err)
59 | }
60 |
61 | fmt.Println(string(j))
62 | // Output:
63 | // {
64 | // "definitions":{
65 | // "WeekDay":{
66 | // "enum":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
67 | // "type":"string"
68 | // }
69 | // },
70 | // "properties":{
71 | // "day":{
72 | // "enum":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
73 | // "type":"string"
74 | // },
75 | // "days":{"items":{"$ref":"#/definitions/WeekDay"},"type":"array"},
76 | // "days2":{
77 | // "items":{"type":"string"},
78 | // "enum":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
79 | // "type":"array"
80 | // }
81 | // },
82 | // "type":"object"
83 | // }
84 | }
85 |
--------------------------------------------------------------------------------
/example_exposer_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/swaggest/assertjson"
7 | "github.com/swaggest/jsonschema-go"
8 | )
9 |
10 | // ParentOfExposer is an example structure.
11 | type ParentOfExposer struct {
12 | Bar Exposer `json:"bar"`
13 | }
14 |
15 | // Exposer is an example structure.
16 | type Exposer struct {
17 | Foo string `json:"foo"`
18 | }
19 |
20 | var _ jsonschema.Exposer = Exposer{}
21 |
22 | // JSONSchema returns raw JSON Schema bytes.
23 | // Fields and tags of structure are ignored.
24 | func (Exposer) JSONSchema() (jsonschema.Schema, error) {
25 | var schema jsonschema.Schema
26 |
27 | schema.AddType(jsonschema.Object)
28 | schema.WithDescription("Custom description.")
29 |
30 | return schema, nil
31 | }
32 |
33 | func ExampleExposer() {
34 | reflector := jsonschema.Reflector{}
35 |
36 | s, err := reflector.Reflect(ParentOfExposer{}, jsonschema.StripDefinitionNamePrefix("JsonschemaGoTest"))
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | j, err := assertjson.MarshalIndentCompact(s, "", " ", 80)
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | fmt.Println(string(j))
47 | // Output:
48 | // {
49 | // "definitions":{"Exposer":{"description":"Custom description.","type":"object"}},
50 | // "properties":{"bar":{"$ref":"#/definitions/Exposer"}},"type":"object"
51 | // }
52 | }
53 |
--------------------------------------------------------------------------------
/example_oneofexposer_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/swaggest/assertjson"
7 | "github.com/swaggest/jsonschema-go"
8 | )
9 |
10 | // ParentOfOneOfExposer is an example structure.
11 | type ParentOfOneOfExposer struct {
12 | Bar OneOfExposer `json:"bar"`
13 | }
14 |
15 | // OneOfExposer is an example structure.
16 | type OneOfExposer struct{}
17 |
18 | type OneOf1 struct {
19 | Foo string `json:"foo" required:"true"`
20 | }
21 |
22 | type OneOf2 struct {
23 | Baz string `json:"baz" required:"true"`
24 | }
25 |
26 | var _ jsonschema.OneOfExposer = OneOfExposer{}
27 |
28 | func (OneOfExposer) JSONSchemaOneOf() []interface{} {
29 | return []interface{}{
30 | OneOf1{}, OneOf2{},
31 | }
32 | }
33 |
34 | func ExampleOneOfExposer() {
35 | reflector := jsonschema.Reflector{}
36 |
37 | s, err := reflector.Reflect(ParentOfOneOfExposer{}, jsonschema.StripDefinitionNamePrefix("JsonschemaGoTest"))
38 | if err != nil {
39 | panic(err)
40 | }
41 |
42 | j, err := assertjson.MarshalIndentCompact(s, "", " ", 80)
43 | if err != nil {
44 | panic(err)
45 | }
46 |
47 | fmt.Println(string(j))
48 | // Output:
49 | // {
50 | // "definitions":{
51 | // "OneOf1":{
52 | // "required":["foo"],"properties":{"foo":{"type":"string"}},"type":"object"
53 | // },
54 | // "OneOf2":{
55 | // "required":["baz"],"properties":{"baz":{"type":"string"}},"type":"object"
56 | // },
57 | // "OneOfExposer":{
58 | // "type":"object",
59 | // "oneOf":[{"$ref":"#/definitions/OneOf1"},{"$ref":"#/definitions/OneOf2"}]
60 | // }
61 | // },
62 | // "properties":{"bar":{"$ref":"#/definitions/OneOfExposer"}},"type":"object"
63 | // }
64 | }
65 |
--------------------------------------------------------------------------------
/example_preparer_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/swaggest/assertjson"
7 | "github.com/swaggest/jsonschema-go"
8 | )
9 |
10 | // ParentOfPreparer is an example structure.
11 | type ParentOfPreparer struct {
12 | Bar Preparer `json:"bar"`
13 | }
14 |
15 | // Preparer is an example structure.
16 | type Preparer struct {
17 | Foo string `json:"foo"`
18 | }
19 |
20 | var _ jsonschema.Preparer = Preparer{}
21 |
22 | func (s Preparer) PrepareJSONSchema(schema *jsonschema.Schema) error {
23 | schema.WithDescription("Custom description.")
24 | schema.Properties["foo"].TypeObject.WithEnum("one", "two", "three")
25 |
26 | return nil
27 | }
28 |
29 | func ExamplePreparer() {
30 | reflector := jsonschema.Reflector{}
31 |
32 | s, err := reflector.Reflect(ParentOfPreparer{}, jsonschema.StripDefinitionNamePrefix("JsonschemaGoTest"))
33 | if err != nil {
34 | panic(err)
35 | }
36 |
37 | j, err := assertjson.MarshalIndentCompact(s, "", " ", 80)
38 | if err != nil {
39 | panic(err)
40 | }
41 |
42 | fmt.Println(string(j))
43 | // Output:
44 | // {
45 | // "definitions":{
46 | // "Preparer":{
47 | // "description":"Custom description.",
48 | // "properties":{"foo":{"enum":["one","two","three"],"type":"string"}},
49 | // "type":"object"
50 | // }
51 | // },
52 | // "properties":{"bar":{"$ref":"#/definitions/Preparer"}},"type":"object"
53 | // }
54 | }
55 |
--------------------------------------------------------------------------------
/example_rawexposer_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/swaggest/assertjson"
7 | "github.com/swaggest/jsonschema-go"
8 | )
9 |
10 | // ParentOfRawExposer is an example structure.
11 | type ParentOfRawExposer struct {
12 | Bar RawExposer `json:"bar"`
13 | }
14 |
15 | // RawExposer is an example structure.
16 | type RawExposer struct {
17 | Foo string `json:"foo"`
18 | }
19 |
20 | var _ jsonschema.RawExposer = RawExposer{}
21 |
22 | // JSONSchemaBytes returns raw JSON Schema bytes.
23 | // Fields and tags of structure are ignored.
24 | func (s RawExposer) JSONSchemaBytes() ([]byte, error) {
25 | return []byte(`{"description":"Custom description.","type":"object","properties":{"foo":{"type":"string"}}}`), nil
26 | }
27 |
28 | func ExampleRawExposer() {
29 | reflector := jsonschema.Reflector{}
30 |
31 | s, err := reflector.Reflect(ParentOfRawExposer{}, jsonschema.StripDefinitionNamePrefix("JsonschemaGoTest"))
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | j, err := assertjson.MarshalIndentCompact(s, "", " ", 80)
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | fmt.Println(string(j))
42 | // Output:
43 | // {
44 | // "definitions":{
45 | // "RawExposer":{
46 | // "description":"Custom description.",
47 | // "properties":{"foo":{"type":"string"}},"type":"object"
48 | // }
49 | // },
50 | // "properties":{"bar":{"$ref":"#/definitions/RawExposer"}},"type":"object"
51 | // }
52 | }
53 |
--------------------------------------------------------------------------------
/example_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "reflect"
8 | "strings"
9 |
10 | "github.com/swaggest/assertjson"
11 | "github.com/swaggest/jsonschema-go"
12 | )
13 |
14 | // WeirdResp hides sample structure.
15 | type WeirdResp interface {
16 | Boo()
17 | }
18 |
19 | // NamedAnything is an empty interface.
20 | type NamedAnything interface{}
21 |
22 | // UUID represents type owned by 3rd party library.
23 | type UUID [16]byte
24 |
25 | // Resp is a sample structure.
26 | type Resp struct {
27 | Field1 int `json:"field1"`
28 | Field2 string `json:"field2"`
29 | Info struct {
30 | Foo string `json:"foo" default:"baz" required:"true" pattern:"\\d+"`
31 | Bar float64 `json:"bar" description:"This is Bar."`
32 | } `json:"info"`
33 | Parent *Resp `json:"parent,omitempty"`
34 | Map map[string]int64 `json:"map,omitempty"`
35 | MapOfAnything map[string]interface{} `json:"mapOfAnything,omitempty"`
36 | ArrayOfAnything []interface{} `json:"arrayOfAnything,omitempty"`
37 | ArrayOfNamedAnything []NamedAnything `json:"arrayOfNamedAnything,omitempty"`
38 | Whatever interface{} `json:"whatever"`
39 | NullableWhatever *interface{} `json:"nullableWhatever,omitempty"`
40 | RecursiveArray []WeirdResp `json:"recursiveArray,omitempty"`
41 | RecursiveStructArray []Resp `json:"recursiveStructArray,omitempty"`
42 | UUID UUID `json:"uuid"`
43 | }
44 |
45 | // Description implements jsonschema.Described.
46 | func (r *Resp) Description() string {
47 | return "This is a sample response."
48 | }
49 |
50 | // Title implements jsonschema.Titled.
51 | func (r *Resp) Title() string {
52 | return "Sample Response"
53 | }
54 |
55 | var _ jsonschema.Preparer = &Resp{}
56 |
57 | func (r *Resp) PrepareJSONSchema(s *jsonschema.Schema) error {
58 | s.WithExtraPropertiesItem("x-foo", "bar")
59 |
60 | return nil
61 | }
62 |
63 | func ExampleReflector_InlineDefinition() {
64 | reflector := jsonschema.Reflector{}
65 |
66 | // Create custom schema mapping for 3rd party type.
67 | uuidDef := jsonschema.Schema{}
68 | uuidDef.AddType(jsonschema.String)
69 | uuidDef.WithFormat("uuid")
70 | uuidDef.WithExamples("248df4b7-aa70-47b8-a036-33ac447e668d")
71 |
72 | // Map 3rd party type with your own schema.
73 | reflector.AddTypeMapping(UUID{}, uuidDef)
74 | reflector.InlineDefinition(UUID{})
75 |
76 | type MyStruct struct {
77 | ID UUID `json:"id"`
78 | }
79 |
80 | schema, _ := reflector.Reflect(MyStruct{})
81 |
82 | schemaJSON, _ := json.MarshalIndent(schema, "", " ")
83 |
84 | fmt.Println(string(schemaJSON))
85 | // Output:
86 | // {
87 | // "properties": {
88 | // "id": {
89 | // "examples": [
90 | // "248df4b7-aa70-47b8-a036-33ac447e668d"
91 | // ],
92 | // "type": "string",
93 | // "format": "uuid"
94 | // }
95 | // },
96 | // "type": "object"
97 | // }
98 | }
99 |
100 | func ExampleReflector_AddTypeMapping_schema() {
101 | reflector := jsonschema.Reflector{}
102 |
103 | // Create custom schema mapping for 3rd party type.
104 | uuidDef := jsonschema.Schema{}
105 | uuidDef.AddType(jsonschema.String)
106 | uuidDef.WithFormat("uuid")
107 | uuidDef.WithExamples("248df4b7-aa70-47b8-a036-33ac447e668d")
108 |
109 | // Map 3rd party type with your own schema.
110 | reflector.AddTypeMapping(UUID{}, uuidDef)
111 |
112 | type MyStruct struct {
113 | ID UUID `json:"id"`
114 | }
115 |
116 | schema, _ := reflector.Reflect(MyStruct{})
117 |
118 | schemaJSON, _ := json.MarshalIndent(schema, "", " ")
119 |
120 | fmt.Println(string(schemaJSON))
121 | // Output:
122 | // {
123 | // "definitions": {
124 | // "JsonschemaGoTestUUID": {
125 | // "examples": [
126 | // "248df4b7-aa70-47b8-a036-33ac447e668d"
127 | // ],
128 | // "type": "string",
129 | // "format": "uuid"
130 | // }
131 | // },
132 | // "properties": {
133 | // "id": {
134 | // "$ref": "#/definitions/JsonschemaGoTestUUID"
135 | // }
136 | // },
137 | // "type": "object"
138 | // }
139 | }
140 |
141 | func ExampleReflector_AddTypeMapping_type() {
142 | reflector := jsonschema.Reflector{}
143 |
144 | // Map 3rd party type with a different type.
145 | // Reflector will perceive all UUIDs as plain strings.
146 | reflector.AddTypeMapping(UUID{}, "")
147 |
148 | type MyStruct struct {
149 | ID UUID `json:"id"`
150 | }
151 |
152 | schema, _ := reflector.Reflect(MyStruct{})
153 |
154 | schemaJSON, _ := json.MarshalIndent(schema, "", " ")
155 |
156 | fmt.Println(string(schemaJSON))
157 | // Output:
158 | // {
159 | // "properties": {
160 | // "id": {
161 | // "type": "string"
162 | // }
163 | // },
164 | // "type": "object"
165 | // }
166 | }
167 |
168 | func ExampleReflector_Reflect() {
169 | reflector := jsonschema.Reflector{}
170 |
171 | // Create custom schema mapping for 3rd party type.
172 | uuidDef := jsonschema.Schema{}
173 | uuidDef.AddType(jsonschema.String)
174 | uuidDef.WithFormat("uuid")
175 | uuidDef.WithExamples("248df4b7-aa70-47b8-a036-33ac447e668d")
176 |
177 | // Map 3rd party type with your own schema.
178 | reflector.AddTypeMapping(UUID{}, uuidDef)
179 |
180 | // Map the type that does not expose schema information to a type with schema information.
181 | reflector.AddTypeMapping(new(WeirdResp), new(Resp))
182 |
183 | // Modify default definition names to better match your packages structure.
184 | reflector.DefaultOptions = append(reflector.DefaultOptions, jsonschema.InterceptDefName(
185 | func(_ reflect.Type, defaultDefName string) string {
186 | return strings.TrimPrefix(defaultDefName, "JsonschemaGoTest")
187 | },
188 | ))
189 |
190 | // Create schema from Go value.
191 | schema, err := reflector.Reflect(new(Resp))
192 | if err != nil {
193 | log.Fatal(err)
194 | }
195 |
196 | j, err := assertjson.MarshalIndentCompact(schema, "", " ", 80)
197 | if err != nil {
198 | log.Fatal(err)
199 | }
200 |
201 | fmt.Println(string(j))
202 |
203 | // Output:
204 | // {
205 | // "title":"Sample Response","description":"This is a sample response.",
206 | // "definitions":{
207 | // "NamedAnything":{},
208 | // "UUID":{
209 | // "examples":["248df4b7-aa70-47b8-a036-33ac447e668d"],"type":"string",
210 | // "format":"uuid"
211 | // }
212 | // },
213 | // "properties":{
214 | // "arrayOfAnything":{"items":{},"type":"array"},
215 | // "arrayOfNamedAnything":{"items":{"$ref":"#/definitions/NamedAnything"},"type":"array"},
216 | // "field1":{"type":"integer"},"field2":{"type":"string"},
217 | // "info":{
218 | // "required":["foo"],
219 | // "properties":{
220 | // "bar":{"description":"This is Bar.","type":"number"},
221 | // "foo":{"default":"baz","pattern":"\\d+","type":"string"}
222 | // },
223 | // "type":"object"
224 | // },
225 | // "map":{"additionalProperties":{"type":"integer"},"type":"object"},
226 | // "mapOfAnything":{"additionalProperties":{},"type":"object"},
227 | // "nullableWhatever":{},"parent":{"$ref":"#"},
228 | // "recursiveArray":{"items":{"$ref":"#"},"type":"array"},
229 | // "recursiveStructArray":{"items":{"$ref":"#"},"type":"array"},
230 | // "uuid":{"$ref":"#/definitions/UUID"},"whatever":{}
231 | // },
232 | // "type":"object","x-foo":"bar"
233 | // }
234 | }
235 |
236 | func ExampleReflector_Reflect_simple() {
237 | type MyStruct struct {
238 | Amount float64 `json:"amount" minimum:"10.5" example:"20.6" required:"true"`
239 | Abc string `json:"abc" pattern:"[abc]"`
240 | _ struct{} `additionalProperties:"false"` // Tags of unnamed field are applied to parent schema.
241 | _ struct{} `title:"My Struct" description:"Holds my data."` // Multiple unnamed fields can be used.
242 | }
243 |
244 | reflector := jsonschema.Reflector{}
245 |
246 | schema, err := reflector.Reflect(MyStruct{})
247 | if err != nil {
248 | log.Fatal(err)
249 | }
250 |
251 | j, err := json.MarshalIndent(schema, "", " ")
252 | if err != nil {
253 | log.Fatal(err)
254 | }
255 |
256 | fmt.Println(string(j))
257 |
258 | // Output:
259 | // {
260 | // "title": "My Struct",
261 | // "description": "Holds my data.",
262 | // "required": [
263 | // "amount"
264 | // ],
265 | // "additionalProperties": false,
266 | // "properties": {
267 | // "abc": {
268 | // "pattern": "[abc]",
269 | // "type": "string"
270 | // },
271 | // "amount": {
272 | // "examples": [
273 | // 20.6
274 | // ],
275 | // "minimum": 10.5,
276 | // "type": "number"
277 | // }
278 | // },
279 | // "type": "object"
280 | // }
281 | }
282 |
283 | func ExamplePropertyNameMapping() {
284 | reflector := jsonschema.Reflector{}
285 |
286 | type Test struct {
287 | ID int `minimum:"123" default:"200"`
288 | Name string `minLength:"10"`
289 | }
290 |
291 | s, err := reflector.Reflect(new(Test),
292 | // PropertyNameMapping allows configuring property names without field tag.
293 | jsonschema.PropertyNameMapping(map[string]string{
294 | "ID": "ident",
295 | "Name": "last_name",
296 | }))
297 | if err != nil {
298 | panic(err)
299 | }
300 |
301 | j, err := assertjson.MarshalIndentCompact(s, "", " ", 80)
302 | if err != nil {
303 | panic(err)
304 | }
305 |
306 | fmt.Println(string(j))
307 | // Output:
308 | // {
309 | // "properties":{
310 | // "ident":{"default":200,"minimum":123,"type":"integer"},
311 | // "last_name":{"minLength":10,"type":"string"}
312 | // },
313 | // "type":"object"
314 | // }
315 | }
316 |
317 | func ExampleInterceptProp() {
318 | reflector := jsonschema.Reflector{}
319 |
320 | type Test struct {
321 | ID int `json:"id" minimum:"123" default:"200"`
322 | Name string `json:"name" minLength:"10"`
323 | Skipped float64 `json:"skipped"`
324 | }
325 |
326 | s, err := reflector.Reflect(new(Test),
327 | // PropertyNameMapping allows configuring property names without field tag.
328 | jsonschema.InterceptProp(
329 | func(params jsonschema.InterceptPropParams) error {
330 | switch params.Name {
331 | // You can alter reflected schema by updating propertySchema.
332 | case "id":
333 | if params.Processed {
334 | params.PropertySchema.WithDescription("This is ID.")
335 | // You can access schema that holds the property.
336 | params.PropertySchema.Parent.WithDescription("Schema with ID.")
337 | }
338 |
339 | // Or you can entirely remove property from parent schema with a sentinel error.
340 | case "skipped":
341 | return jsonschema.ErrSkipProperty
342 | }
343 |
344 | return nil
345 | },
346 | ),
347 | )
348 | if err != nil {
349 | panic(err)
350 | }
351 |
352 | j, err := assertjson.MarshalIndentCompact(s, "", " ", 80)
353 | if err != nil {
354 | panic(err)
355 | }
356 |
357 | fmt.Println(string(j))
358 | // Output:
359 | // {
360 | // "description":"Schema with ID.",
361 | // "properties":{
362 | // "id":{"description":"This is ID.","default":200,"minimum":123,"type":"integer"},
363 | // "name":{"minLength":10,"type":"string"}
364 | // },
365 | // "type":"object"
366 | // }
367 | }
368 |
369 | func ExampleOneOf() {
370 | r := jsonschema.Reflector{}
371 |
372 | type Test struct {
373 | Foo jsonschema.OneOfExposer `json:"foo"`
374 | Bar jsonschema.OneOfExposer `json:"bar"`
375 | }
376 |
377 | tt := Test{
378 | Foo: jsonschema.OneOf(1.23, "abc"),
379 | Bar: jsonschema.OneOf(123, true),
380 | }
381 |
382 | s, _ := r.Reflect(tt, jsonschema.RootRef)
383 | b, _ := assertjson.MarshalIndentCompact(s, "", " ", 100)
384 |
385 | fmt.Println("Complex schema:", string(b))
386 |
387 | s, _ = r.Reflect(jsonschema.OneOf(123, true), jsonschema.RootRef)
388 | b, _ = assertjson.MarshalIndentCompact(s, "", " ", 100)
389 |
390 | fmt.Println("Simple schema:", string(b))
391 |
392 | // Output:
393 | // Complex schema: {
394 | // "$ref":"#/definitions/JsonschemaGoTestTest",
395 | // "definitions":{
396 | // "JsonschemaGoTestTest":{
397 | // "properties":{
398 | // "bar":{"oneOf":[{"type":"integer"},{"type":"boolean"}]},
399 | // "foo":{"oneOf":[{"type":"number"},{"type":"string"}]}
400 | // },
401 | // "type":"object"
402 | // }
403 | // }
404 | // }
405 | // Simple schema: {"oneOf":[{"type":"integer"},{"type":"boolean"}]}
406 | }
407 |
408 | func ExampleAnyOf() {
409 | r := jsonschema.Reflector{}
410 |
411 | type Test struct {
412 | Foo jsonschema.AnyOfExposer `json:"foo"`
413 | Bar jsonschema.AnyOfExposer `json:"bar"`
414 | }
415 |
416 | tt := Test{
417 | Foo: jsonschema.AnyOf(1.23, "abc"),
418 | Bar: jsonschema.AnyOf(123, true),
419 | }
420 |
421 | s, _ := r.Reflect(tt, jsonschema.RootRef)
422 | b, _ := assertjson.MarshalIndentCompact(s, "", " ", 100)
423 |
424 | fmt.Println("Complex schema:", string(b))
425 |
426 | s, _ = r.Reflect(jsonschema.AnyOf(123, true), jsonschema.RootRef)
427 | b, _ = assertjson.MarshalIndentCompact(s, "", " ", 100)
428 |
429 | fmt.Println("Simple schema:", string(b))
430 |
431 | // Output:
432 | // Complex schema: {
433 | // "$ref":"#/definitions/JsonschemaGoTestTest",
434 | // "definitions":{
435 | // "JsonschemaGoTestTest":{
436 | // "properties":{
437 | // "bar":{"anyOf":[{"type":"integer"},{"type":"boolean"}]},
438 | // "foo":{"anyOf":[{"type":"number"},{"type":"string"}]}
439 | // },
440 | // "type":"object"
441 | // }
442 | // }
443 | // }
444 | // Simple schema: {"anyOf":[{"type":"integer"},{"type":"boolean"}]}
445 | }
446 |
447 | func ExampleAllOf() {
448 | r := jsonschema.Reflector{}
449 |
450 | type Test struct {
451 | Foo jsonschema.AllOfExposer `json:"foo"`
452 | Bar jsonschema.AllOfExposer `json:"bar"`
453 | }
454 |
455 | tt := Test{
456 | Foo: jsonschema.AllOf(1.23, "abc"),
457 | Bar: jsonschema.AllOf(123, true),
458 | }
459 |
460 | s, _ := r.Reflect(tt, jsonschema.RootRef)
461 | b, _ := assertjson.MarshalIndentCompact(s, "", " ", 100)
462 |
463 | fmt.Println("Complex schema:", string(b))
464 |
465 | s, _ = r.Reflect(jsonschema.AllOf(123, true), jsonschema.RootRef)
466 | b, _ = assertjson.MarshalIndentCompact(s, "", " ", 100)
467 |
468 | fmt.Println("Simple schema:", string(b))
469 |
470 | // Output:
471 | // Complex schema: {
472 | // "$ref":"#/definitions/JsonschemaGoTestTest",
473 | // "definitions":{
474 | // "JsonschemaGoTestTest":{
475 | // "properties":{
476 | // "bar":{"allOf":[{"type":"integer"},{"type":"boolean"}]},
477 | // "foo":{"allOf":[{"type":"number"},{"type":"string"}]}
478 | // },
479 | // "type":"object"
480 | // }
481 | // }
482 | // }
483 | // Simple schema: {"allOf":[{"type":"integer"},{"type":"boolean"}]}
484 | }
485 |
486 | func ExampleReflector_Reflect_default() {
487 | type MyStruct struct {
488 | A []string `json:"a" default:"[A,B,C]"` // For an array of strings, comma-separated values in square brackets can be used.
489 | B []int `json:"b" default:"[1,2,3]"` // Other non-scalar values are parsed as JSON without type checking.
490 | C []string `json:"c" default:"[\"C\",\"B\",\"A\"]"`
491 | D int `json:"d" default:"123"` // Scalar values are parsed according to their type.
492 | E string `json:"e" default:"abc"`
493 | F map[string]int `json:"f" default:"{\"foo\":1,\"bar\":2}"`
494 | }
495 |
496 | type Invalid struct {
497 | I []int `json:"i" default:"[C,B,A]"` // Value with invalid JSON is ignored for types other than []string (and equivalent).
498 | }
499 |
500 | r := jsonschema.Reflector{}
501 | s, _ := r.Reflect(MyStruct{})
502 | _, err := r.Reflect(Invalid{})
503 |
504 | j, _ := assertjson.MarshalIndentCompact(s, "", " ", 80)
505 |
506 | fmt.Println("MyStruct:", string(j))
507 | fmt.Println("Invalid error:", err.Error())
508 | // Output:
509 | // MyStruct: {
510 | // "properties":{
511 | // "a":{"default":["A","B","C"],"items":{"type":"string"},"type":["array","null"]},
512 | // "b":{"default":[1,2,3],"items":{"type":"integer"},"type":["array","null"]},
513 | // "c":{"default":["C","B","A"],"items":{"type":"string"},"type":["array","null"]},
514 | // "d":{"default":123,"type":"integer"},"e":{"default":"abc","type":"string"},
515 | // "f":{
516 | // "default":{"bar":2,"foo":1},"additionalProperties":{"type":"integer"},
517 | // "type":["object","null"]
518 | // }
519 | // },
520 | // "type":"object"
521 | // }
522 | // Invalid error: I: parsing default as JSON: invalid character 'C' looking for beginning of value
523 | }
524 |
525 | func ExampleReflector_Reflect_virtualStruct() {
526 | s := jsonschema.Struct{}
527 | s.SetTitle("Test title")
528 | s.SetDescription("Test description")
529 | s.DefName = "TestStruct"
530 | s.Nullable = true
531 |
532 | s.Fields = append(s.Fields, jsonschema.Field{
533 | Name: "Foo",
534 | Value: "abc",
535 | Tag: `json:"foo" minLength:"3"`,
536 | })
537 |
538 | r := jsonschema.Reflector{}
539 | schema, _ := r.Reflect(s)
540 | j, _ := assertjson.MarshalIndentCompact(schema, "", " ", 80)
541 |
542 | fmt.Println("Standalone:", string(j))
543 |
544 | type MyStruct struct {
545 | jsonschema.Struct // Can be embedded.
546 |
547 | Bar int `json:"bar"`
548 |
549 | Nested jsonschema.Struct `json:"nested"` // Can be nested.
550 | }
551 |
552 | ms := MyStruct{}
553 | ms.Nested = s
554 | ms.Struct = s
555 |
556 | schema, _ = r.Reflect(ms)
557 | j, _ = assertjson.MarshalIndentCompact(schema, "", " ", 80)
558 |
559 | fmt.Println("Nested:", string(j))
560 |
561 | // Output:
562 | // Standalone: {
563 | // "title":"Test title","description":"Test description",
564 | // "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
565 | // }
566 | // Nested: {
567 | // "title":"Test title","description":"Test description",
568 | // "properties":{
569 | // "bar":{"type":"integer"},"foo":{"minLength":3,"type":"string"},
570 | // "nested":{"$ref":"#"}
571 | // },
572 | // "type":"object"
573 | // }
574 | }
575 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/swaggest/jsonschema-go
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/bool64/dev v0.2.39
7 | github.com/stretchr/testify v1.8.2
8 | github.com/swaggest/assertjson v1.9.0
9 | github.com/swaggest/refl v1.4.0
10 | github.com/yudai/gojsondiff v1.0.0
11 | )
12 |
13 | require (
14 | github.com/bool64/shared v0.1.5 // indirect
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/iancoleman/orderedmap v0.3.0 // indirect
17 | github.com/mattn/go-isatty v0.0.14 // indirect
18 | github.com/nxadm/tail v1.4.8 // indirect
19 | github.com/pmezard/go-difflib v1.0.0 // indirect
20 | github.com/sergi/go-diff v1.3.1 // indirect
21 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
22 | golang.org/x/net v0.7.0 // indirect
23 | golang.org/x/sys v0.5.0 // indirect
24 | golang.org/x/text v0.13.0 // indirect
25 | gopkg.in/yaml.v3 v3.0.1 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE=
2 | github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
3 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
4 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
9 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
10 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
11 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
12 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
13 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
15 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
16 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
17 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
18 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
19 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
20 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
21 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
22 | github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org=
23 | github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug=
24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
26 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
27 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
28 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
29 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
30 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
31 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
32 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
33 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
34 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
35 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
36 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
37 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
38 | github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k=
39 | github.com/swaggest/refl v1.4.0/go.mod h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA=
40 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
41 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
42 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
43 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
44 | github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
45 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
46 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
47 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
48 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
49 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
50 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
51 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
52 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
54 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
55 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
56 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
57 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
58 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
59 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
60 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
61 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
62 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
63 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
64 |
--------------------------------------------------------------------------------
/helper.go:
--------------------------------------------------------------------------------
1 | package jsonschema
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "reflect"
7 | )
8 |
9 | const (
10 | // XEnumNames is the name of JSON property to store names of enumerated values.
11 | XEnumNames = "x-enum-names"
12 | )
13 |
14 | // NamedEnum returns the enumerated acceptable values with according string names.
15 | type NamedEnum interface {
16 | NamedEnum() ([]interface{}, []string)
17 | }
18 |
19 | // Enum returns the enumerated acceptable values.
20 | type Enum interface {
21 | Enum() []interface{}
22 | }
23 |
24 | // Preparer alters reflected JSON Schema.
25 | type Preparer interface {
26 | PrepareJSONSchema(schema *Schema) error
27 | }
28 |
29 | // Exposer exposes JSON Schema.
30 | type Exposer interface {
31 | JSONSchema() (Schema, error)
32 | }
33 |
34 | // RawExposer exposes JSON Schema as JSON bytes.
35 | type RawExposer interface {
36 | JSONSchemaBytes() ([]byte, error)
37 | }
38 |
39 | // OneOfExposer exposes "oneOf" items as list of samples.
40 | type OneOfExposer interface {
41 | JSONSchemaOneOf() []interface{}
42 | }
43 |
44 | // AnyOfExposer exposes "anyOf" items as list of samples.
45 | type AnyOfExposer interface {
46 | JSONSchemaAnyOf() []interface{}
47 | }
48 |
49 | // AllOfExposer exposes "allOf" items as list of samples.
50 | type AllOfExposer interface {
51 | JSONSchemaAllOf() []interface{}
52 | }
53 |
54 | // NotExposer exposes "not" schema as a sample.
55 | type NotExposer interface {
56 | JSONSchemaNot() interface{}
57 | }
58 |
59 | // IfExposer exposes "if" schema as a sample.
60 | type IfExposer interface {
61 | JSONSchemaIf() interface{}
62 | }
63 |
64 | // ThenExposer exposes "then" schema as a sample.
65 | type ThenExposer interface {
66 | JSONSchemaThen() interface{}
67 | }
68 |
69 | // ElseExposer exposes "else" schema as a sample.
70 | type ElseExposer interface {
71 | JSONSchemaElse() interface{}
72 | }
73 |
74 | // JSONSchema implements Exposer.
75 | func (s Schema) JSONSchema() (Schema, error) {
76 | // Making a deep copy of Schema with JSON round trip to avoid unintentional sharing of pointer data.
77 | j, err := json.Marshal(s)
78 | if err != nil {
79 | return Schema{}, fmt.Errorf("deepcopy marshal: %w", err)
80 | }
81 |
82 | var c Schema
83 |
84 | if err := json.Unmarshal(j, &c); err != nil {
85 | return Schema{}, fmt.Errorf("deepcopy unmarshal: %w", err)
86 | }
87 |
88 | return c, nil
89 | }
90 |
91 | // ToSchemaOrBool creates SchemaOrBool instance from Schema.
92 | func (s *Schema) ToSchemaOrBool() SchemaOrBool {
93 | return SchemaOrBool{
94 | TypeObject: s,
95 | }
96 | }
97 |
98 | // Type references simple type.
99 | func (i SimpleType) Type() Type {
100 | return Type{SimpleTypes: &i}
101 | }
102 |
103 | // ToSchemaOrBool creates SchemaOrBool instance from SimpleType.
104 | func (i SimpleType) ToSchemaOrBool() SchemaOrBool {
105 | return SchemaOrBool{
106 | TypeObject: (&Schema{}).WithType(i.Type()),
107 | }
108 | }
109 |
110 | // RemoveType removes simple type from Schema.
111 | //
112 | // If there is no type, no change is made.
113 | func (s *Schema) RemoveType(t SimpleType) {
114 | if s.Type == nil {
115 | return
116 | }
117 |
118 | if s.Type.SimpleTypes != nil {
119 | if *s.Type.SimpleTypes == t {
120 | s.Type = nil
121 | }
122 |
123 | return
124 | }
125 |
126 | if len(s.Type.SliceOfSimpleTypeValues) > 0 {
127 | var tt []SimpleType
128 |
129 | for _, st := range s.Type.SliceOfSimpleTypeValues {
130 | if st != t {
131 | tt = append(tt, st)
132 | }
133 | }
134 |
135 | if len(tt) == 1 {
136 | s.Type.SimpleTypes = &tt[0]
137 | s.Type.SliceOfSimpleTypeValues = nil
138 | } else {
139 | s.Type.SliceOfSimpleTypeValues = tt
140 | }
141 | }
142 | }
143 |
144 | // AddType adds simple type to Schema.
145 | //
146 | // If type is already there it is ignored.
147 | func (s *Schema) AddType(t SimpleType) {
148 | if s.Type == nil {
149 | s.WithType(t.Type())
150 |
151 | return
152 | }
153 |
154 | if s.Type.SimpleTypes != nil {
155 | if *s.Type.SimpleTypes == t {
156 | return
157 | }
158 |
159 | s.Type.SliceOfSimpleTypeValues = []SimpleType{*s.Type.SimpleTypes, t}
160 | s.Type.SimpleTypes = nil
161 |
162 | return
163 | }
164 |
165 | if len(s.Type.SliceOfSimpleTypeValues) > 0 {
166 | for _, st := range s.Type.SliceOfSimpleTypeValues {
167 | if st == t {
168 | return
169 | }
170 | }
171 |
172 | s.Type.SliceOfSimpleTypeValues = append(s.Type.SliceOfSimpleTypeValues, t)
173 | }
174 | }
175 |
176 | // IsTrivial is true if schema does not contain validation constraints other than type.
177 | func (s SchemaOrBool) IsTrivial(refResolvers ...func(string) (SchemaOrBool, bool)) bool {
178 | if s.TypeBoolean != nil && !*s.TypeBoolean {
179 | return false
180 | }
181 |
182 | if s.TypeObject != nil {
183 | return s.TypeObject.IsTrivial(refResolvers...)
184 | }
185 |
186 | return true
187 | }
188 |
189 | // IsTrivial is true if schema does not contain validation constraints other than type.
190 | //
191 | // Trivial schema can define trivial items or properties.
192 | // This flag can be used to skip validation of structures that check types during decoding.
193 | func (s Schema) IsTrivial(refResolvers ...func(string) (SchemaOrBool, bool)) bool {
194 | if len(s.AllOf) > 0 || len(s.AnyOf) > 0 || len(s.OneOf) > 0 || s.Not != nil ||
195 | s.If != nil || s.Then != nil || s.Else != nil {
196 | return false
197 | }
198 |
199 | if s.Minimum != nil {
200 | if *s.Minimum != 0 || s.ReflectType == nil {
201 | return false
202 | }
203 |
204 | //nolint:exhaustive // Allow trivial schema non-negative integers backed by uint*.
205 | switch s.ReflectType.Kind() {
206 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
207 | break
208 | default:
209 | return false
210 | }
211 | }
212 |
213 | if s.MultipleOf != nil || s.Maximum != nil ||
214 | s.ExclusiveMinimum != nil || s.ExclusiveMaximum != nil {
215 | return false
216 | }
217 |
218 | if s.MinLength != 0 || s.MaxLength != nil || s.Pattern != nil || s.Format != nil {
219 | return false
220 | }
221 |
222 | if s.MinItems != 0 || s.MaxItems != nil || s.UniqueItems != nil || s.Contains != nil {
223 | return false
224 | }
225 |
226 | if s.MinProperties != 0 || s.MaxProperties != nil || len(s.Required) > 0 || len(s.PatternProperties) > 0 {
227 | return false
228 | }
229 |
230 | if len(s.Dependencies) > 0 || s.PropertyNames != nil || s.Const != nil || len(s.Enum) > 0 {
231 | return false
232 | }
233 |
234 | if s.Type != nil && len(s.Type.SliceOfSimpleTypeValues) > 1 && !s.HasType(Null) {
235 | return false
236 | }
237 |
238 | if s.Items != nil && (len(s.Items.SchemaArray) > 0 || (s.Items.SchemaOrBool != nil && !s.Items.SchemaOrBool.IsTrivial(refResolvers...))) {
239 | return false
240 | }
241 |
242 | if s.AdditionalItems != nil && !s.AdditionalItems.IsTrivial(refResolvers...) {
243 | return false
244 | }
245 |
246 | if s.AdditionalProperties != nil && !s.AdditionalProperties.IsTrivial(refResolvers...) {
247 | return false
248 | }
249 |
250 | if len(s.Properties) > 0 {
251 | for _, ps := range s.Properties {
252 | if !ps.IsTrivial(refResolvers...) {
253 | return false
254 | }
255 | }
256 | }
257 |
258 | if s.Ref == nil {
259 | return true
260 | }
261 |
262 | resolved := false
263 |
264 | // If same ref is met, it is returned as trivial schema to avoid duplicate recursion.
265 | skipRef := func(ref string) (SchemaOrBool, bool) {
266 | if ref == *s.Ref {
267 | return SchemaOrBool{}, true
268 | }
269 |
270 | return SchemaOrBool{}, false
271 | }
272 |
273 | rr := append([]func(ref string) (SchemaOrBool, bool){skipRef}, refResolvers...)
274 |
275 | for _, resolve := range refResolvers {
276 | if rs, found := resolve(*s.Ref); found {
277 | resolved = true
278 |
279 | if !rs.IsTrivial(rr...) {
280 | return false
281 | }
282 |
283 | break
284 | }
285 | }
286 |
287 | return resolved
288 | }
289 |
290 | // HasType checks if Schema has a simple type.
291 | func (s *Schema) HasType(t SimpleType) bool {
292 | if s.Type == nil {
293 | return false
294 | }
295 |
296 | if s.Type.SimpleTypes != nil {
297 | return *s.Type.SimpleTypes == t
298 | }
299 |
300 | if len(s.Type.SliceOfSimpleTypeValues) > 0 {
301 | for _, st := range s.Type.SliceOfSimpleTypeValues {
302 | if st == t {
303 | return true
304 | }
305 | }
306 | }
307 |
308 | return false
309 | }
310 |
311 | // JSONSchemaBytes exposes JSON Schema as raw JSON bytes.
312 | func (s SchemaOrBool) JSONSchemaBytes() ([]byte, error) {
313 | return json.Marshal(s)
314 | }
315 |
316 | // JSONSchemaBytes exposes JSON Schema as raw JSON bytes.
317 | func (s Schema) JSONSchemaBytes() ([]byte, error) {
318 | return json.Marshal(s)
319 | }
320 |
321 | // ToSimpleMap encodes JSON Schema as a map.
322 | func (s SchemaOrBool) ToSimpleMap() (map[string]interface{}, error) {
323 | var m map[string]interface{}
324 |
325 | if s.TypeBoolean != nil {
326 | if *s.TypeBoolean {
327 | return map[string]interface{}{}, nil
328 | }
329 |
330 | return map[string]interface{}{
331 | "not": map[string]interface{}{},
332 | }, nil
333 | }
334 |
335 | b, err := json.Marshal(s.TypeObject)
336 | if err != nil {
337 | return nil, err
338 | }
339 |
340 | err = json.Unmarshal(b, &m)
341 | if err != nil {
342 | return nil, err
343 | }
344 |
345 | return m, nil
346 | }
347 |
348 | // FromSimpleMap decodes JSON Schema from a map.
349 | func (s *SchemaOrBool) FromSimpleMap(m map[string]interface{}) error {
350 | j, err := json.Marshal(m)
351 | if err != nil {
352 | return err
353 | }
354 |
355 | s.TypeBoolean = nil
356 |
357 | return json.Unmarshal(j, s.TypeObjectEns())
358 | }
359 |
--------------------------------------------------------------------------------
/helper_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "encoding/json"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | "github.com/swaggest/jsonschema-go"
11 | )
12 |
13 | func TestSchemaOrBool_JSONSchemaBytes(t *testing.T) {
14 | s := jsonschema.Schema{}
15 | s.AddType(jsonschema.String)
16 |
17 | b, err := s.ToSchemaOrBool().JSONSchemaBytes()
18 | require.NoError(t, err)
19 | assert.Equal(t, `{"type":"string"}`, string(b))
20 |
21 | b, err = s.JSONSchemaBytes()
22 | require.NoError(t, err)
23 | assert.Equal(t, `{"type":"string"}`, string(b))
24 |
25 | m, err := s.ToSchemaOrBool().ToSimpleMap()
26 | require.NoError(t, err)
27 | assert.Equal(t, map[string]interface{}{"type": "string"}, m)
28 |
29 | var sb jsonschema.SchemaOrBool
30 |
31 | sb.WithTypeBoolean(true)
32 | require.NoError(t, sb.FromSimpleMap(m))
33 | assert.Nil(t, sb.TypeBoolean)
34 | require.NotNil(t, sb.TypeObject)
35 | assert.True(t, sb.TypeObject.HasType(jsonschema.String))
36 |
37 | sbf := jsonschema.SchemaOrBool{}
38 | sbf.WithTypeBoolean(false)
39 | m, err = sbf.ToSimpleMap()
40 | require.NoError(t, err)
41 | assert.Equal(t, map[string]interface{}{"not": map[string]interface{}{}}, m)
42 |
43 | sbt := jsonschema.SchemaOrBool{}
44 | sbt.WithTypeBoolean(true)
45 | m, err = sbt.ToSimpleMap()
46 | require.NoError(t, err)
47 | assert.Equal(t, map[string]interface{}{}, m)
48 | }
49 |
50 | func TestSchema_IsTrivial(t *testing.T) {
51 | for _, s := range []struct {
52 | isTrivial bool
53 | name string
54 | schema string
55 | }{
56 | {true, "true schema", "true"},
57 | {false, "false schema", "false"},
58 | {true, "empty schema", "{}"},
59 | {true, "type object", `{"type":"object", "additionalProperties":{"type":"integer"}}`},
60 | {
61 | false, "type object with non-trivial members",
62 | `{"type":"object", "additionalProperties":{"type":"integer","minimum":3}}`,
63 | },
64 | {
65 | true, "type object with properties",
66 | `{"type":"object", "properties":{"foo":{"type":"integer"}}}`,
67 | },
68 | {
69 | false, "type object with non-trivial members",
70 | `{"type":"object", "properties":{"foo":{"type":"integer","minimum":3}}}`,
71 | },
72 | {false, "type fixed array", `{"type":"array", "items":[{"type":"string"}]}`},
73 | {true, "type array", `{"type":"array", "items":{"type":"string"}}`},
74 | {
75 | false, "type array with non-trivial members",
76 | `{"type":"array", "items":{"type":"string", "format":"email"}}`,
77 | },
78 | {true, "type array additionalItems", `{"type":"array", "additionalItems":{"type":"string"}}`},
79 | {
80 | false, "type array additionalItems with non-trivial members",
81 | `{"type":"array", "additionalItems":{"type":"string", "format":"email"}}`,
82 | },
83 | {true, "scalar type", `{"type":"integer"}`},
84 | {true, "scalar nullable type", `{"type":["integer", "null"]}`},
85 | {false, "scalar types", `{"type":["integer", "string"]}`},
86 | {false, "with format", `{"format":"email"}`},
87 | {false, "with not", `{"not":true}`},
88 | {false, "with allOf", `{"allOf":[true]}`},
89 | {false, "with enum", `{"enum":[1,2,3]}`},
90 | {false, "with minItems", `{"minItems":5}`},
91 | {false, "with minProperties", `{"minProperties":5}`},
92 | {false, "with $ref", `{"$ref":"#/definitions/foo","definitions":{"foo":true}}`},
93 | } {
94 | s := s
95 |
96 | t.Run(s.name, func(t *testing.T) {
97 | var schema jsonschema.SchemaOrBool
98 |
99 | require.NoError(t, json.Unmarshal([]byte(s.schema), &schema))
100 | assert.Equal(t, s.isTrivial, schema.IsTrivial())
101 | })
102 | }
103 | }
104 |
105 | func TestSchema_IsTrivial_reflect(t *testing.T) {
106 | type inner struct {
107 | A uint32 `json:"a"`
108 | }
109 |
110 | type outer struct {
111 | I inner `json:"inner"`
112 | }
113 |
114 | r := jsonschema.Reflector{}
115 |
116 | s, err := r.Reflect(new(outer))
117 | require.NoError(t, err)
118 |
119 | assert.True(t, s.IsTrivial(func(ref string) (jsonschema.SchemaOrBool, bool) {
120 | rs, found := s.Definitions[strings.TrimPrefix(ref, "#/definitions/")]
121 |
122 | return rs, found
123 | }))
124 | }
125 |
126 | func TestSchema_IsTrivial_recursive(t *testing.T) {
127 | type Lvl2 struct {
128 | Scalar int `json:"scalar" minimum:"100"`
129 | }
130 |
131 | type Lvl1 struct {
132 | Scalar string `json:"scalar"`
133 | Recursion1 []Lvl1 `json:"l1s"`
134 | L2 []Lvl2 `json:"bs"`
135 | }
136 |
137 | type TopLevel struct {
138 | L1 Lvl1 `json:"asd"`
139 | }
140 |
141 | r := jsonschema.Reflector{}
142 |
143 | s, err := r.Reflect(TopLevel{})
144 | require.NoError(t, err)
145 |
146 | assert.False(t, s.IsTrivial(func(ref string) (jsonschema.SchemaOrBool, bool) {
147 | rs, found := s.Definitions[strings.TrimPrefix(ref, "#/definitions/")]
148 |
149 | return rs, found
150 | }))
151 | }
152 |
153 | func TestSchema_IsTrivial_recursiveTrivial(t *testing.T) {
154 | type Lvl2 struct {
155 | Scalar int `json:"scalar"`
156 | }
157 |
158 | type Lvl1 struct {
159 | Scalar string `json:"scalar"`
160 | Recursion1 []Lvl1 `json:"l1s"`
161 | L2 []Lvl2 `json:"bs"`
162 | }
163 |
164 | type TopLevel struct {
165 | L1 Lvl1 `json:"asd"`
166 | }
167 |
168 | r := jsonschema.Reflector{}
169 |
170 | s, err := r.Reflect(TopLevel{})
171 | require.NoError(t, err)
172 |
173 | assert.True(t, s.IsTrivial(func(ref string) (jsonschema.SchemaOrBool, bool) {
174 | rs, found := s.Definitions[strings.TrimPrefix(ref, "#/definitions/")]
175 |
176 | return rs, found
177 | }))
178 | }
179 |
--------------------------------------------------------------------------------
/reflect_go1.18_test.go:
--------------------------------------------------------------------------------
1 | //go:build go1.18
2 | // +build go1.18
3 |
4 | package jsonschema_test
5 |
6 | import (
7 | "net/netip"
8 | "testing"
9 | "time"
10 |
11 | "github.com/stretchr/testify/require"
12 | "github.com/swaggest/assertjson"
13 | "github.com/swaggest/jsonschema-go"
14 | )
15 |
16 | func TestReflector_Reflect_generic(t *testing.T) {
17 | type helloOutput struct {
18 | Now time.Time `header:"X-Now" json:"-"`
19 | Message string `json:"message"`
20 | }
21 |
22 | type helloOutput2 struct {
23 | Bar string `json:"bar"`
24 | }
25 |
26 | type APIResponse[T any] struct {
27 | Data *T `json:"data"`
28 | }
29 |
30 | var ar struct {
31 | Foo APIResponse[helloOutput] `json:"foo"`
32 | Bar APIResponse[helloOutput2] `json:"bar"`
33 | }
34 |
35 | r := jsonschema.Reflector{}
36 |
37 | s, err := r.Reflect(ar, jsonschema.StripDefinitionNamePrefix("JsonschemaGoTest"))
38 | require.NoError(t, err)
39 | assertjson.EqualMarshal(t, []byte(`{
40 | "definitions":{
41 | "APIResponse[HelloOutput2]":{
42 | "properties":{"data":{"$ref":"#/definitions/HelloOutput2"}},
43 | "type":"object"
44 | },
45 | "APIResponse[HelloOutput]":{
46 | "properties":{"data":{"$ref":"#/definitions/HelloOutput"}},
47 | "type":"object"
48 | },
49 | "HelloOutput":{"properties":{"message":{"type":"string"}},"type":"object"},
50 | "HelloOutput2":{"properties":{"bar":{"type":"string"}},"type":"object"}
51 | },
52 | "properties":{
53 | "bar":{"$ref":"#/definitions/APIResponse[HelloOutput2]"},
54 | "foo":{"$ref":"#/definitions/APIResponse[HelloOutput]"}
55 | },
56 | "type":"object"
57 | }`), s)
58 |
59 | r = jsonschema.Reflector{}
60 | s, err = r.Reflect(ar)
61 | require.NoError(t, err)
62 | assertjson.EqualMarshal(t, []byte(`{
63 | "definitions":{
64 | "JsonschemaGoTestAPIResponse[JsonschemaGoTestHelloOutput2]":{
65 | "properties":{"data":{"$ref":"#/definitions/JsonschemaGoTestHelloOutput2"}},
66 | "type":"object"
67 | },
68 | "JsonschemaGoTestAPIResponse[JsonschemaGoTestHelloOutput]":{
69 | "properties":{"data":{"$ref":"#/definitions/JsonschemaGoTestHelloOutput"}},
70 | "type":"object"
71 | },
72 | "JsonschemaGoTestHelloOutput":{"properties":{"message":{"type":"string"}},"type":"object"},
73 | "JsonschemaGoTestHelloOutput2":{"properties":{"bar":{"type":"string"}},"type":"object"}
74 | },
75 | "properties":{
76 | "bar":{
77 | "$ref":"#/definitions/JsonschemaGoTestAPIResponse[JsonschemaGoTestHelloOutput2]"
78 | },
79 | "foo":{
80 | "$ref":"#/definitions/JsonschemaGoTestAPIResponse[JsonschemaGoTestHelloOutput]"
81 | }
82 | },
83 | "type":"object"
84 | }`), s)
85 | }
86 |
87 | func TestReflector_Reflect_fieldTags(t *testing.T) {
88 | type My struct {
89 | Prefix netip.Prefix `json:"prefix" required:"true" example:"192.168.0.0/24" description:"Prefix in CIDR notation" format:"cidr"`
90 | }
91 |
92 | reflector := jsonschema.Reflector{}
93 |
94 | s, err := reflector.Reflect(My{})
95 | require.NoError(t, err)
96 | assertjson.EqualMarshal(t, []byte(`{
97 | "required":["prefix"],
98 | "properties":{
99 | "prefix":{
100 | "type":"string",
101 | "description":"Prefix in CIDR notation","examples":["192.168.0.0/24"],
102 | "format":"cidr"
103 | }
104 | },
105 | "type":"object"
106 | }`), s)
107 | }
108 |
--------------------------------------------------------------------------------
/resources/schema/draft-07.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "http://json-schema.org/draft-07/schema#",
4 | "title": "Core schema meta-schema",
5 | "definitions": {
6 | "schemaArray": {
7 | "type": "array",
8 | "minItems": 1,
9 | "items": { "$ref": "#" }
10 | },
11 | "nonNegativeInteger": {
12 | "type": "integer",
13 | "minimum": 0
14 | },
15 | "nonNegativeIntegerDefault0": {
16 | "allOf": [
17 | { "$ref": "#/definitions/nonNegativeInteger" },
18 | { "default": 0 }
19 | ]
20 | },
21 | "simpleTypes": {
22 | "title": "Simple Type",
23 | "enum": [
24 | "array",
25 | "boolean",
26 | "integer",
27 | "null",
28 | "number",
29 | "object",
30 | "string"
31 | ]
32 | },
33 | "stringArray": {
34 | "type": "array",
35 | "items": { "type": "string" },
36 | "uniqueItems": true,
37 | "default": []
38 | }
39 | },
40 | "type": ["object", "boolean"],
41 | "properties": {
42 | "$id": {
43 | "type": "string",
44 | "format": "uri-reference"
45 | },
46 | "$schema": {
47 | "type": "string",
48 | "format": "uri"
49 | },
50 | "$ref": {
51 | "type": "string",
52 | "format": "uri-reference"
53 | },
54 | "$comment": {
55 | "type": "string"
56 | },
57 | "title": {
58 | "type": "string"
59 | },
60 | "description": {
61 | "type": "string"
62 | },
63 | "default": true,
64 | "readOnly": {
65 | "type": "boolean",
66 | "default": false
67 | },
68 | "examples": {
69 | "type": "array",
70 | "items": true
71 | },
72 | "multipleOf": {
73 | "type": "number",
74 | "exclusiveMinimum": 0
75 | },
76 | "maximum": {
77 | "type": "number"
78 | },
79 | "exclusiveMaximum": {
80 | "type": "number"
81 | },
82 | "minimum": {
83 | "type": "number"
84 | },
85 | "exclusiveMinimum": {
86 | "type": "number"
87 | },
88 | "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
89 | "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
90 | "pattern": {
91 | "type": "string",
92 | "format": "regex"
93 | },
94 | "additionalItems": { "$ref": "#" },
95 | "items": {
96 | "anyOf": [
97 | { "$ref": "#" },
98 | { "$ref": "#/definitions/schemaArray" }
99 | ],
100 | "default": true
101 | },
102 | "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
103 | "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
104 | "uniqueItems": {
105 | "type": "boolean",
106 | "default": false
107 | },
108 | "contains": { "$ref": "#" },
109 | "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
110 | "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
111 | "required": { "$ref": "#/definitions/stringArray" },
112 | "additionalProperties": { "$ref": "#" },
113 | "definitions": {
114 | "type": "object",
115 | "additionalProperties": { "$ref": "#" },
116 | "default": {}
117 | },
118 | "properties": {
119 | "type": "object",
120 | "additionalProperties": { "$ref": "#" },
121 | "default": {}
122 | },
123 | "patternProperties": {
124 | "type": "object",
125 | "additionalProperties": { "$ref": "#" },
126 | "propertyNames": { "format": "regex" },
127 | "default": {}
128 | },
129 | "dependencies": {
130 | "type": "object",
131 | "additionalProperties": {
132 | "anyOf": [
133 | { "$ref": "#" },
134 | { "$ref": "#/definitions/stringArray" }
135 | ]
136 | }
137 | },
138 | "propertyNames": { "$ref": "#" },
139 | "const": true,
140 | "enum": {
141 | "type": "array",
142 | "items": true,
143 | "minItems": 1,
144 | "uniqueItems": true
145 | },
146 | "type": {
147 | "anyOf": [
148 | { "$ref": "#/definitions/simpleTypes" },
149 | {
150 | "type": "array",
151 | "items": { "$ref": "#/definitions/simpleTypes" },
152 | "minItems": 1,
153 | "uniqueItems": true
154 | }
155 | ]
156 | },
157 | "format": { "type": "string" },
158 | "contentMediaType": { "type": "string" },
159 | "contentEncoding": { "type": "string" },
160 | "if": { "$ref": "#" },
161 | "then": { "$ref": "#" },
162 | "else": { "$ref": "#" },
163 | "allOf": { "$ref": "#/definitions/schemaArray" },
164 | "anyOf": { "$ref": "#/definitions/schemaArray" },
165 | "oneOf": { "$ref": "#/definitions/schemaArray" },
166 | "not": { "$ref": "#" }
167 | },
168 | "default": true
169 | }
170 |
--------------------------------------------------------------------------------
/struct.go:
--------------------------------------------------------------------------------
1 | package jsonschema
2 |
3 | import (
4 | "reflect"
5 | "strconv"
6 |
7 | "github.com/swaggest/refl"
8 | )
9 |
10 | var structDefaultDefNameIndex = 0
11 |
12 | // Field mimics Go reflect.StructField for purposes of schema reflection.
13 | type Field struct {
14 | Name string
15 | Value interface{}
16 | Tag reflect.StructTag
17 | }
18 |
19 | // Struct mimics Go struct to allow schema reflection on virtual struct type.
20 | //
21 | // This can be handy for dynamic values that can not be represented as static Go structures.
22 | type Struct struct {
23 | Title *string
24 | Description *string
25 | Nullable bool
26 | DefName string
27 |
28 | Fields []Field
29 | }
30 |
31 | // SetTitle sets title.
32 | func (s *Struct) SetTitle(title string) {
33 | s.Title = &title
34 | }
35 |
36 | // SetDescription sets description.
37 | func (s *Struct) SetDescription(description string) {
38 | s.Description = &description
39 | }
40 |
41 | type withStruct interface {
42 | structPtr() *Struct
43 | }
44 |
45 | func (s Struct) structPtr() *Struct {
46 | return &s
47 | }
48 |
49 | func (s Struct) names() (string, refl.TypeString) {
50 | defName := s.DefName
51 |
52 | if defName == "" {
53 | structDefaultDefNameIndex++
54 |
55 | defName = "struct" + strconv.Itoa(structDefaultDefNameIndex)
56 | }
57 |
58 | return defName, refl.TypeString("struct." + defName)
59 | }
60 |
--------------------------------------------------------------------------------
/struct_test.go:
--------------------------------------------------------------------------------
1 | package jsonschema_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | "github.com/swaggest/assertjson"
8 | "github.com/swaggest/jsonschema-go"
9 | )
10 |
11 | func TestReflector_Reflect_Struct(t *testing.T) {
12 | r := jsonschema.Reflector{}
13 |
14 | s := jsonschema.Struct{}
15 | s.SetTitle("Test title")
16 | s.SetDescription("Test description")
17 | s.DefName = "TestStruct"
18 | s.Nullable = true
19 |
20 | s.Fields = append(s.Fields, jsonschema.Field{
21 | Name: "Foo",
22 | Value: "abc",
23 | Tag: `json:"fo0" minLength:"3"`,
24 | })
25 |
26 | s.Fields = append(s.Fields, jsonschema.Field{
27 | Name: "Bar",
28 | Value: 123,
29 | Tag: `json:"b4r" minimum:"3"`,
30 | })
31 |
32 | s.Fields = append(s.Fields, jsonschema.Field{
33 | Name: "Baz",
34 | Value: []int{},
35 | Tag: `json:"b4z" minItems:"4"`,
36 | })
37 |
38 | s.Fields = append(s.Fields, jsonschema.Field{
39 | Name: "Pers",
40 | Value: Person{},
41 | Tag: `json:"pers"`,
42 | })
43 |
44 | s.Fields = append(s.Fields, jsonschema.Field{
45 | Name: "Recursion",
46 | Value: s,
47 | Tag: `json:"recursion"`,
48 | })
49 |
50 | s2 := jsonschema.Struct{}
51 | s2.SetTitle("T2")
52 | s2.DefName = "TestStruct2"
53 |
54 | s2.Fields = append(s2.Fields, jsonschema.Field{
55 | Name: "Quux",
56 | Value: "abc",
57 | Tag: `json:"quux" minLength:"3"`,
58 | })
59 |
60 | s.Fields = append(s.Fields, jsonschema.Field{
61 | Name: "Other",
62 | Value: s2,
63 | Tag: `json:"other"`,
64 | })
65 |
66 | s2.DefName = ""
67 |
68 | s.Fields = append(s.Fields, jsonschema.Field{
69 | Name: "Another",
70 | Value: s2,
71 | Tag: `json:"another"`,
72 | })
73 |
74 | sc, err := r.Reflect(s)
75 | require.NoError(t, err)
76 |
77 | assertjson.EqMarshal(t, `{
78 | "title":"Test title","description":"Test description",
79 | "definitions":{
80 | "JsonschemaGoTestEnumed":{"enum":["foo","bar"],"type":"string"},
81 | "JsonschemaGoTestPerson":{
82 | "title":"Person","required":["lastName"],
83 | "properties":{
84 | "birthDate":{"type":"string","format":"date"},
85 | "createdAt":{"type":"string","format":"date-time"},
86 | "date":{"type":"string","format":"date"},
87 | "deathDate":{"type":["null","string"],"format":"date"},
88 | "deletedAt":{"type":["null","string"],"format":"date-time"},
89 | "enumed":{"$ref":"#/definitions/JsonschemaGoTestEnumed"},
90 | "enumedPtr":{"$ref":"#/definitions/JsonschemaGoTestEnumed"},
91 | "firstName":{"type":"string"},"height":{"type":"integer"},
92 | "lastName":{"type":"string"},"meta":{},
93 | "role":{"description":"The role of person.","type":"string"}
94 | },
95 | "type":"object"
96 | },
97 | "TestStruct2":{
98 | "title":"T2","properties":{"quux":{"minLength":3,"type":"string"}},
99 | "type":"object"
100 | },
101 | "struct1":{
102 | "title":"T2","properties":{"quux":{"minLength":3,"type":"string"}},
103 | "type":"object"
104 | }
105 | },
106 | "properties":{
107 | "another":{"$ref":"#/definitions/struct1"},
108 | "b4r":{"minimum":3,"type":"integer"},
109 | "b4z":{"items":{"type":"integer"},"minItems":4,"type":["array","null"]},
110 | "fo0":{"minLength":3,"type":"string"},
111 | "other":{"$ref":"#/definitions/TestStruct2"},
112 | "pers":{"$ref":"#/definitions/JsonschemaGoTestPerson"},
113 | "recursion":{"$ref":"#"}
114 | },
115 | "type":"object"
116 | }`, sc)
117 | }
118 |
119 | func TestReflector_Reflect_StructEmbed(t *testing.T) {
120 | type dynamicInput struct {
121 | jsonschema.Struct
122 |
123 | // Type is a static field example.
124 | Type string `query:"type"`
125 | }
126 |
127 | type dynamicOutput struct {
128 | // Embedded jsonschema.Struct exposes dynamic fields for documentation.
129 | jsonschema.Struct
130 |
131 | jsonFields map[string]interface{}
132 | headerFields map[string]string
133 |
134 | // Status is a static field example.
135 | Status string `json:"status"`
136 | }
137 |
138 | dynIn := dynamicInput{}
139 | dynIn.DefName = "DynIn123"
140 | dynIn.Struct.Fields = []jsonschema.Field{
141 | {Name: "Foo", Value: 123, Tag: `header:"foo" enum:"123,456,789"`},
142 | {Name: "Bar", Value: "abc", Tag: `query:"bar"`},
143 | }
144 |
145 | dynOut := dynamicOutput{}
146 | dynOut.DefName = "DynOut123"
147 | dynOut.Struct.Fields = []jsonschema.Field{
148 | {Name: "Foo", Value: 123, Tag: `header:"foo" enum:"123,456,789"`},
149 | {Name: "Bar", Value: "abc", Tag: `json:"bar"`},
150 | }
151 |
152 | type S struct {
153 | In dynamicInput `json:"in"`
154 | Out dynamicOutput `json:"out"`
155 | }
156 |
157 | s := S{
158 | In: dynIn,
159 | Out: dynOut,
160 | }
161 |
162 | r := jsonschema.Reflector{}
163 |
164 | ss, err := r.Reflect(s, func(rc *jsonschema.ReflectContext) {
165 | rc.PropertyNameTag = "json"
166 | rc.PropertyNameAdditionalTags = []string{"header", "query"}
167 | })
168 | require.NoError(t, err)
169 |
170 | assertjson.EqMarshal(t, `{
171 | "definitions":{
172 | "DynIn123":{
173 | "properties":{
174 | "bar":{"type":"string"},"foo":{"enum":[123,456,789],"type":"integer"},
175 | "type":{"type":"string"}
176 | },
177 | "type":"object"
178 | },
179 | "DynOut123":{
180 | "properties":{
181 | "bar":{"type":"string"},"foo":{"enum":[123,456,789],"type":"integer"},
182 | "status":{"type":"string"}
183 | },
184 | "type":"object"
185 | }
186 | },
187 | "properties":{
188 | "in":{"$ref":"#/definitions/DynIn123"},
189 | "out":{"$ref":"#/definitions/DynOut123"}
190 | },
191 | "type":"object"
192 | }`, ss)
193 | }
194 |
195 | func TestReflector_Reflect_StructExample(t *testing.T) {
196 | s := jsonschema.Struct{}
197 | s.SetTitle("Test title")
198 | s.SetDescription("Test description")
199 | s.DefName = "TestStruct"
200 | s.Nullable = true
201 |
202 | s.Fields = append(s.Fields, jsonschema.Field{
203 | Name: "Foo",
204 | Value: "abc",
205 | Tag: `json:"foo" minLength:"3"`,
206 | })
207 |
208 | r := jsonschema.Reflector{}
209 |
210 | t.Run("standalone", func(t *testing.T) {
211 | schema, err := r.Reflect(s)
212 | require.NoError(t, err)
213 |
214 | assertjson.EqMarshal(t, `{
215 | "title":"Test title","description":"Test description",
216 | "properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"
217 | }`, schema)
218 | })
219 |
220 | type MyStruct struct {
221 | jsonschema.Struct // Can be structPtr.
222 |
223 | Bar int `json:"bar"`
224 |
225 | Nested jsonschema.Struct `json:"nested"` // Can be nested.
226 | }
227 |
228 | ms := MyStruct{}
229 | ms.Nested = s
230 | ms.Struct = s
231 |
232 | t.Run("nested", func(t *testing.T) {
233 | schema, err := r.Reflect(ms)
234 | require.NoError(t, err)
235 | assertjson.EqMarshal(t, `{
236 | "title":"Test title","description":"Test description",
237 | "properties":{
238 | "bar":{"type":"integer"},"foo":{"minLength":3,"type":"string"},
239 | "nested":{"$ref":"#"}
240 | },
241 | "type":"object"
242 | }`, schema)
243 | })
244 | }
245 |
--------------------------------------------------------------------------------