├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── workflows │ ├── bench.yml │ ├── cloc.yml │ ├── golangci-lint.yml │ ├── gorelease.yml │ └── test-unit.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── camelcase.go ├── context.go ├── date.go ├── date_test.go ├── dev_test.go ├── doc.go ├── entities.go ├── entities_extra_test.go ├── entities_test.go ├── example_enum_test.go ├── example_exposer_test.go ├── example_oneofexposer_test.go ├── example_preparer_test.go ├── example_rawexposer_test.go ├── example_test.go ├── go.mod ├── go.sum ├── helper.go ├── helper_test.go ├── reflect.go ├── reflect_go1.18_test.go ├── reflect_test.go ├── resources └── schema │ └── draft-07.json ├── struct.go └── struct_test.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | If feasible/relevant, please provide a code snippet (inline or with Go playground) to reproduce the issue. 15 | 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Additional context** 21 | Add any other context about the problem here. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please use discussions https://github.com/swaggest/jsonschema-go/discussions/categories/ideas to share feature ideas. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Any question about features or usage 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please use discussions https://github.com/swaggest/jsonschema-go/discussions/categories/q-a to make your question more discoverable by other folks. 11 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: bench 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | inputs: 7 | old: 8 | description: 'Old Ref' 9 | required: false 10 | default: 'master' 11 | new: 12 | description: 'New Ref' 13 | required: true 14 | 15 | env: 16 | GO111MODULE: "on" 17 | CACHE_BENCHMARK: "off" # Enables benchmark result reuse between runs, may skew latency results. 18 | RUN_BASE_BENCHMARK: "on" # Runs benchmark for PR base in case benchmark result is missing. 19 | jobs: 20 | bench: 21 | strategy: 22 | matrix: 23 | go-version: [ stable ] 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Install Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: ${{ matrix.go-version }} 30 | - name: Checkout code 31 | uses: actions/checkout@v4 32 | with: 33 | ref: ${{ (github.event.inputs.new != '') && github.event.inputs.new || github.event.ref }} 34 | - name: Go cache 35 | uses: actions/cache@v4 36 | with: 37 | # In order: 38 | # * Module download cache 39 | # * Build cache (Linux) 40 | path: | 41 | ~/go/pkg/mod 42 | ~/.cache/go-build 43 | key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.sum') }} 44 | restore-keys: | 45 | ${{ runner.os }}-go-cache 46 | - name: Restore benchstat 47 | uses: actions/cache@v4 48 | with: 49 | path: ~/go/bin/benchstat 50 | key: ${{ runner.os }}-benchstat 51 | - name: Restore base benchmark result 52 | if: env.CACHE_BENCHMARK == 'on' 53 | id: benchmark-base 54 | uses: actions/cache@v4 55 | with: 56 | path: | 57 | bench-master.txt 58 | bench-main.txt 59 | # Use base sha for PR or new commit hash for master/main push in benchmark result key. 60 | key: ${{ runner.os }}-bench-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }} 61 | - name: Checkout base code 62 | if: env.RUN_BASE_BENCHMARK == 'on' && steps.benchmark-base.outputs.cache-hit != 'true' && (github.event.pull_request.base.sha != '' || github.event.inputs.old != '') 63 | uses: actions/checkout@v4 64 | with: 65 | ref: ${{ (github.event.pull_request.base.sha != '' ) && github.event.pull_request.base.sha || github.event.inputs.old }} 66 | path: __base 67 | - name: Run base benchmark 68 | if: env.RUN_BASE_BENCHMARK == 'on' && steps.benchmark-base.outputs.cache-hit != 'true' && (github.event.pull_request.base.sha != '' || github.event.inputs.old != '') 69 | run: | 70 | export REF_NAME=master 71 | cd __base 72 | make | grep bench-run && (BENCH_COUNT=5 make bench-run bench-stat && cp bench-master.txt ../bench-master.txt) || echo "No benchmarks in base" 73 | - name: Benchmark 74 | id: bench 75 | run: | 76 | export REF_NAME=new 77 | BENCH_COUNT=5 make bench 78 | OUTPUT=$(make bench-stat-diff) 79 | OUTPUT="${OUTPUT//'%'/'%25'}" 80 | OUTPUT="${OUTPUT//$'\n'/'%0A'}" 81 | OUTPUT="${OUTPUT//$'\r'/'%0D'}" 82 | echo "::set-output name=diff::$OUTPUT" 83 | OUTPUT=$(make bench-stat) 84 | OUTPUT="${OUTPUT//'%'/'%25'}" 85 | OUTPUT="${OUTPUT//$'\n'/'%0A'}" 86 | OUTPUT="${OUTPUT//$'\r'/'%0D'}" 87 | echo "::set-output name=result::$OUTPUT" 88 | - name: Comment Benchmark Result 89 | uses: marocchino/sticky-pull-request-comment@v2 90 | with: 91 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 92 | header: bench 93 | message: | 94 | ### Benchmark Result 95 |
Benchmark diff with base branch 96 | 97 | ``` 98 | ${{ steps.bench.outputs.diff }} 99 | ``` 100 |
101 | 102 |
Benchmark result 103 | 104 | ``` 105 | ${{ steps.bench.outputs.result }} 106 | ``` 107 |
108 | -------------------------------------------------------------------------------- /.github/workflows/cloc.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: cloc 3 | on: 4 | pull_request: 5 | 6 | # Cancel the workflow in progress in newer build is about to start. 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | cloc: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | path: pr 19 | - name: Checkout base code 20 | uses: actions/checkout@v4 21 | with: 22 | ref: ${{ github.event.pull_request.base.sha }} 23 | path: base 24 | - name: Count lines of code 25 | id: loc 26 | run: | 27 | curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.3/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz 28 | sccdiff_hash=$(git hash-object ./sccdiff) 29 | [ "$sccdiff_hash" == "ae8a07b687bd3dba60861584efe724351aa7ff63" ] || (echo "::error::unexpected hash for sccdiff, possible tampering: $sccdiff_hash" && exit 1) 30 | OUTPUT=$(cd pr && ../sccdiff -basedir ../base) 31 | echo "${OUTPUT}" 32 | echo "diff<> $GITHUB_OUTPUT && echo "$OUTPUT" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT 33 | 34 | - name: Comment lines of code 35 | continue-on-error: true 36 | uses: marocchino/sticky-pull-request-comment@v2 37 | with: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | header: LOC 40 | message: | 41 | ### Lines Of Code 42 | 43 | ${{ steps.loc.outputs.diff }} 44 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: lint 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - master 9 | - main 10 | pull_request: 11 | 12 | # Cancel the workflow in progress in newer build is about to start. 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | golangci: 19 | name: golangci-lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/setup-go@v5 23 | with: 24 | go-version: stable 25 | - uses: actions/checkout@v4 26 | - name: golangci-lint 27 | uses: golangci/golangci-lint-action@v6.5.0 28 | with: 29 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 30 | version: v1.64.5 31 | 32 | # Optional: working directory, useful for monorepos 33 | # working-directory: somedir 34 | 35 | # Optional: golangci-lint command line arguments. 36 | # args: --issues-exit-code=0 37 | 38 | # Optional: show only new issues if it's a pull request. The default value is `false`. 39 | # only-new-issues: true 40 | 41 | # Optional: if set to true then the action will use pre-installed Go. 42 | # skip-go-installation: true 43 | 44 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 45 | # skip-pkg-cache: true 46 | 47 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 48 | # skip-build-cache: true -------------------------------------------------------------------------------- /.github/workflows/gorelease.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: gorelease 3 | on: 4 | pull_request: 5 | 6 | # Cancel the workflow in progress in newer build is about to start. 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 9 | cancel-in-progress: true 10 | 11 | env: 12 | GO_VERSION: stable 13 | jobs: 14 | gorelease: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Install Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: ${{ env.GO_VERSION }} 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | - name: Gorelease cache 24 | uses: actions/cache@v4 25 | with: 26 | path: | 27 | ~/go/bin/gorelease 28 | key: ${{ runner.os }}-gorelease-generic 29 | - name: Gorelease 30 | id: gorelease 31 | run: | 32 | test -e ~/go/bin/gorelease || go install golang.org/x/exp/cmd/gorelease@latest 33 | OUTPUT=$(gorelease 2>&1 || exit 0) 34 | echo "${OUTPUT}" 35 | echo "report<> $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 | [![Build Status](https://github.com/swaggest/jsonschema-go/workflows/test-unit/badge.svg)](https://github.com/swaggest/jsonschema-go/actions?query=branch%3Amaster+workflow%3Atest-unit) 6 | [![Coverage Status](https://codecov.io/gh/swaggest/jsonschema-go/branch/master/graph/badge.svg)](https://codecov.io/gh/swaggest/jsonschema-go) 7 | [![GoDevDoc](https://img.shields.io/badge/dev-doc-00ADD8?logo=go)](https://pkg.go.dev/github.com/swaggest/jsonschema-go) 8 | [![time tracker](https://wakatime.com/badge/github/swaggest/jsonschema-go.svg)](https://wakatime.com/badge/github/swaggest/jsonschema-go) 9 | ![Code lines](https://sloc.xyz/github/swaggest/jsonschema-go/?category=code) 10 | ![Comments](https://sloc.xyz/github/swaggest/jsonschema-go/?category=comments) 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 | --------------------------------------------------------------------------------