├── bin ├── hermit.hcl ├── go ├── gofmt ├── .go@latest.pkg ├── .golangci-lint-1.42.1.pkg ├── golangci-lint ├── README.hermit.md ├── activate-hermit └── hermit ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── README.md ├── go.mod ├── fixtures ├── compact_date.json ├── yaml_inline_embed.json ├── custom_type.json ├── schema_with_minimum.json ├── nullable.json ├── custom_additional.json ├── custom_type_with_interface.json ├── test_yaml_and_json.json ├── test_yaml_and_json_prefer_yaml.json ├── custom_slice_type.json ├── yaml_inline.json ├── disable_inlining_embedded.json ├── custom_map_type.json ├── test_yaml_and_json2.json ├── oneof.json ├── go_comments.json ├── defaults_expanded_toplevel.json ├── required_from_jsontags.json ├── ignore_type.json ├── defaults.json ├── allow_additional_props.json ├── fully_qualified.json ├── no_reference.json └── no_ref_qual_types.json ├── examples ├── nested │ └── nested.go └── user.go ├── go.sum ├── COPYING ├── .golangci.yml ├── comment_extractor.go ├── reflect_test.go └── reflect.go /bin/hermit.hcl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/go: -------------------------------------------------------------------------------- 1 | .go@latest.pkg -------------------------------------------------------------------------------- /bin/gofmt: -------------------------------------------------------------------------------- 1 | .go@latest.pkg -------------------------------------------------------------------------------- /bin/.go@latest.pkg: -------------------------------------------------------------------------------- 1 | hermit -------------------------------------------------------------------------------- /bin/.golangci-lint-1.42.1.pkg: -------------------------------------------------------------------------------- 1 | hermit -------------------------------------------------------------------------------- /bin/golangci-lint: -------------------------------------------------------------------------------- 1 | .golangci-lint-1.42.1.pkg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [alecthomas] 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Maintenance of this project has moved to [invopop/jsonschema](https://github.com/invopop/jsonschema). 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alecthomas/jsonschema 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 7 | github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 8 | ) 9 | -------------------------------------------------------------------------------- /bin/README.hermit.md: -------------------------------------------------------------------------------- 1 | # Hermit environment 2 | 3 | This is a [Hermit](https://github.com/cashapp/hermit) bin directory. 4 | 5 | The symlinks in this directory are managed by Hermit and will automatically 6 | download and install Hermit itself as well as packages. These packages are 7 | local to this environment. 8 | -------------------------------------------------------------------------------- /fixtures/compact_date.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/CompactDate", 4 | "definitions": { 5 | "CompactDate": { 6 | "pattern": "^[0-9]{4}-[0-1][0-9]$", 7 | "type": "string", 8 | "title": "Compact Date", 9 | "description": "Short date that only includes year and month" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: CI 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout code 9 | uses: actions/checkout@v2 10 | - name: Init Hermit 11 | run: ./bin/hermit env --raw >> $GITHUB_ENV 12 | - name: Test 13 | run: go test ./... 14 | - name: Lint 15 | run: golangci-lint run 16 | -------------------------------------------------------------------------------- /fixtures/yaml_inline_embed.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/TestYamlInline", 4 | "definitions": { 5 | "TestYamlInline": { 6 | "required": ["foo"], 7 | "properties": { 8 | "foo": { 9 | "type": "string" 10 | } 11 | }, 12 | "additionalProperties": false, 13 | "type": "object" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/nested/nested.go: -------------------------------------------------------------------------------- 1 | package nested 2 | 3 | // Pet defines the user's fury friend. 4 | type Pet struct { 5 | // Name of the animal. 6 | Name string `json:"name" jsonschema:"title=Name"` 7 | } 8 | 9 | type ( 10 | // Plant represents the plants the user might have and serves as a test 11 | // of structs inside a `type` set. 12 | Plant struct { 13 | Variant string `json:"variant" jsonschema:"title=Variant"` // This comment will be ignored 14 | } 15 | ) 16 | -------------------------------------------------------------------------------- /fixtures/custom_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/CustomTypeField", 4 | "definitions": { 5 | "CustomTypeField": { 6 | "required": [ 7 | "CreatedAt" 8 | ], 9 | "properties": { 10 | "CreatedAt": { 11 | "type": "string", 12 | "format": "date-time" 13 | } 14 | }, 15 | "additionalProperties": false, 16 | "type": "object" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fixtures/schema_with_minimum.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/MinValue", 4 | "definitions": { 5 | "MinValue": { 6 | "required": [ 7 | "value4" 8 | ], 9 | "properties": { 10 | "value4": { 11 | "type": "integer", 12 | "minimum": 0 13 | } 14 | }, 15 | "additionalProperties": false, 16 | "type": "object" 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /fixtures/nullable.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/TestNullable", 4 | "definitions": { 5 | "TestNullable": { 6 | "required": ["child1"], 7 | "properties": { 8 | "child1": { 9 | "oneOf": [ 10 | { 11 | "type": "string" 12 | }, 13 | { 14 | "type": "null" 15 | } 16 | ] 17 | } 18 | }, 19 | "additionalProperties": false, 20 | "type": "object" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /fixtures/custom_additional.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 3 | "$ref": "#\/definitions\/GrandfatherType", 4 | "definitions": { 5 | "GrandfatherType": { 6 | "required": [ 7 | "family_name", 8 | "ip_addr" 9 | ], 10 | "properties": { 11 | "family_name": { 12 | "type": "string" 13 | }, 14 | "ip_addr": { 15 | "type": "string", 16 | "format": "ipv4" 17 | } 18 | }, 19 | "additionalProperties": false, 20 | "type": "object" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /fixtures/custom_type_with_interface.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/CustomTypeFieldWithInterface", 4 | "definitions": { 5 | "CustomTypeFieldWithInterface": { 6 | "required": [ 7 | "CreatedAt" 8 | ], 9 | "properties": { 10 | "CreatedAt": { 11 | "$schema": "http://json-schema.org/draft-04/schema#", 12 | "$ref": "#/definitions/CustomTimeWithInterface" 13 | } 14 | }, 15 | "additionalProperties": false, 16 | "type": "object" 17 | }, 18 | "CustomTimeWithInterface": { 19 | "type": "string", 20 | "format": "date-time" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bin/activate-hermit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file must be used with "source bin/activate-hermit" from bash or zsh. 3 | # You cannot run it directly 4 | 5 | if [ "${BASH_SOURCE-}" = "$0" ]; then 6 | echo "You must source this script: \$ source $0" >&2 7 | exit 33 8 | fi 9 | 10 | BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")" 11 | if "${BIN_DIR}/hermit" noop > /dev/null; then 12 | eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")" 13 | 14 | if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then 15 | hash -r 2>/dev/null 16 | fi 17 | 18 | echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated" 19 | fi 20 | -------------------------------------------------------------------------------- /fixtures/test_yaml_and_json.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/TestYamlAndJson", 4 | "definitions": { 5 | "TestYamlAndJson": { 6 | "required": ["FirstName", "LastName", "age"], 7 | "properties": { 8 | "FirstName": { 9 | "type": "string" 10 | }, 11 | "LastName": { 12 | "type": "string" 13 | }, 14 | "age": { 15 | "type": "integer" 16 | }, 17 | "MiddleName": { 18 | "type": "string" 19 | } 20 | }, 21 | "additionalProperties": false, 22 | "type": "object" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /fixtures/test_yaml_and_json_prefer_yaml.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/TestYamlAndJson", 4 | "definitions": { 5 | "TestYamlAndJson": { 6 | "required": ["first_name", "LastName", "age"], 7 | "properties": { 8 | "first_name": { 9 | "type": "string" 10 | }, 11 | "LastName": { 12 | "type": "string" 13 | }, 14 | "age": { 15 | "type": "integer" 16 | }, 17 | "middle_name": { 18 | "type": "string" 19 | } 20 | }, 21 | "additionalProperties": false, 22 | "type": "object" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /fixtures/custom_slice_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/CustomSliceOuter", 4 | "definitions": { 5 | "CustomSliceOuter": { 6 | "type": "object", 7 | "required": [ 8 | "slice" 9 | ], 10 | "additionalProperties": false, 11 | "properties": { 12 | "slice": { 13 | "$schema": "http://json-schema.org/draft-04/schema#", 14 | "$ref": "#/definitions/CustomSliceType" 15 | } 16 | } 17 | }, 18 | "CustomSliceType": { 19 | "oneOf": [ 20 | { 21 | "type": "string" 22 | }, 23 | { 24 | "items": { 25 | "type": "string" 26 | }, 27 | "type": "array" 28 | } 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fixtures/yaml_inline.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/TestYamlInline", 4 | "definitions": { 5 | "Inner": { 6 | "required": ["foo"], 7 | "properties": { 8 | "foo": { 9 | "type": "string" 10 | } 11 | }, 12 | "additionalProperties": false, 13 | "type": "object" 14 | }, 15 | "TestYamlInline": { 16 | "required": [ 17 | "Inlined" 18 | ], 19 | "properties": { 20 | "Inlined": { 21 | "$schema": "http://json-schema.org/draft-04/schema#", 22 | "$ref": "#/definitions/Inner" 23 | } 24 | }, 25 | "additionalProperties": false, 26 | "type": "object" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /fixtures/disable_inlining_embedded.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "required": [ 4 | "inner" 5 | ], 6 | "properties": { 7 | "inner": { 8 | "required": [ 9 | "foo" 10 | ], 11 | "properties": { 12 | "foo": { 13 | "type": "string" 14 | } 15 | }, 16 | "additionalProperties": false, 17 | "type": "object" 18 | } 19 | }, 20 | "additionalProperties": false, 21 | "type": "object", 22 | "definitions": { 23 | "Inner": { 24 | "required": [ 25 | "foo" 26 | ], 27 | "properties": { 28 | "foo": { 29 | "type": "string" 30 | } 31 | }, 32 | "additionalProperties": false, 33 | "type": "object" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /fixtures/custom_map_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/CustomMapOuter", 4 | "definitions": { 5 | "CustomMapOuter": { 6 | "type": "object", 7 | "required": [ 8 | "my_map" 9 | ], 10 | "additionalProperties": false, 11 | "properties": { 12 | "my_map": { 13 | "$schema": "http://json-schema.org/draft-04/schema#", 14 | "$ref": "#/definitions/CustomMapType" 15 | } 16 | } 17 | }, 18 | "CustomMapType": { 19 | "items": { 20 | "required": [ 21 | "key", 22 | "value" 23 | ], 24 | "properties": { 25 | "key": { 26 | "type": "string" 27 | }, 28 | "value": { 29 | "type": "string" 30 | } 31 | }, 32 | "type": "object" 33 | }, 34 | "type": "array" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fixtures/test_yaml_and_json2.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/TestYamlAndJson2", 4 | "definitions": { 5 | "TestYamlAndJson2": { 6 | "required": ["FirstName", "LastName", "age"], 7 | "properties": { 8 | "FirstName": { 9 | "type": "string", 10 | "description": "test2" 11 | }, 12 | "LastName": { 13 | "type": "string", 14 | "description": "test3" 15 | }, 16 | "age": { 17 | "type": "integer", 18 | "description": "test4" 19 | }, 20 | "MiddleName": { 21 | "type": "string", 22 | "description": "test5" 23 | } 24 | }, 25 | "additionalProperties": false, 26 | "type": "object" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bin/hermit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eo pipefail 4 | 5 | if [ -z "${HERMIT_STATE_DIR}" ]; then 6 | case "$(uname -s)" in 7 | Darwin) 8 | export HERMIT_STATE_DIR="${HOME}/Library/Caches/hermit" 9 | ;; 10 | Linux) 11 | export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/hermit" 12 | ;; 13 | esac 14 | fi 15 | 16 | export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}" 17 | HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")" 18 | export HERMIT_CHANNEL 19 | export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit} 20 | 21 | if [ ! -x "${HERMIT_EXE}" ]; then 22 | echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2 23 | curl -fsSL "${HERMIT_DIST_URL}/install.sh" | /bin/bash 1>&2 24 | fi 25 | 26 | exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@" 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= 4 | github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I= 9 | github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 10 | -------------------------------------------------------------------------------- /examples/user.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/alecthomas/jsonschema/examples/nested" 5 | ) 6 | 7 | // User is used as a base to provide tests for comments. 8 | // Don't forget to checkout the nested path. 9 | type User struct { 10 | // Unique sequential identifier. 11 | ID int `json:"id" jsonschema:"required"` 12 | // This comment will be ignored 13 | Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex"` 14 | Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` 15 | Tags map[string]interface{} `json:"tags,omitempty"` 16 | 17 | // An array of pets the user cares for. 18 | Pets []*nested.Pet `json:"pets"` 19 | 20 | // Set of plants that the user likes 21 | Plants []*nested.Plant `json:"plants" jsonschema:"title=Pants"` 22 | } 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Alec Thomas 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /fixtures/oneof.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/RootOneOf", 4 | "definitions": { 5 | "ChildOneOf": { 6 | "properties": { 7 | "child1": { 8 | "type": "string" 9 | }, 10 | "child2": { 11 | "type": "string" 12 | }, 13 | "child3": { 14 | "additionalProperties": true, 15 | "oneOf": [ 16 | { 17 | "type": "string" 18 | }, 19 | { 20 | "type": "array" 21 | } 22 | ] 23 | }, 24 | "child4": { 25 | "type": "string" 26 | } 27 | }, 28 | "additionalProperties": false, 29 | "type": "object", 30 | "oneOf": [ 31 | { 32 | "required": [ 33 | "child1", 34 | "child4" 35 | ], 36 | "title": "group1" 37 | }, 38 | { 39 | "required": [ 40 | "child2", 41 | "child3" 42 | ], 43 | "title": "group2" 44 | } 45 | ] 46 | }, 47 | "RootOneOf": { 48 | "properties": { 49 | "field1": { 50 | "type": "string" 51 | }, 52 | "field2": { 53 | "type": "string" 54 | }, 55 | "field3": { 56 | "additionalProperties": true, 57 | "oneOf": [ 58 | { 59 | "type": "string" 60 | }, 61 | { 62 | "type": "array" 63 | } 64 | ] 65 | }, 66 | "field4": { 67 | "type": "string" 68 | }, 69 | "child": { 70 | "$schema": "http://json-schema.org/draft-04/schema#", 71 | "$ref": "#/definitions/ChildOneOf" 72 | } 73 | }, 74 | "additionalProperties": false, 75 | "type": "object", 76 | "oneOf": [ 77 | { 78 | "required": [ 79 | "field1", 80 | "field4" 81 | ], 82 | "title": "group1" 83 | }, 84 | { 85 | "required": [ 86 | "field2" 87 | ], 88 | "title": "group2" 89 | } 90 | ] 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | max-same-issues: 50 4 | skip-dirs: 5 | - resources 6 | - old 7 | skip-files: 8 | - cmd/protopkg/main.go 9 | 10 | output: 11 | print-issued-lines: false 12 | 13 | linters: 14 | enable-all: true 15 | disable: 16 | - maligned 17 | - megacheck 18 | - lll 19 | - typecheck # `go build` catches this, and it doesn't currently work with Go 1.11 modules 20 | - goimports # horrendously slow with go modules :( 21 | - dupl # has never been actually useful 22 | - gochecknoglobals 23 | - gochecknoinits 24 | - interfacer # author deprecated it because it provides bad suggestions 25 | - funlen 26 | - whitespace 27 | - godox 28 | - wsl 29 | - dogsled 30 | - gomnd 31 | - gocognit 32 | - gocyclo 33 | - scopelint 34 | - godot 35 | - nestif 36 | - testpackage 37 | - goerr113 38 | - gci 39 | - gofumpt 40 | - exhaustivestruct 41 | - nlreturn 42 | - forbidigo 43 | - cyclop 44 | - paralleltest 45 | - ifshort # so annoying 46 | - golint 47 | - tagliatelle 48 | - forcetypeassert 49 | - wrapcheck 50 | - revive 51 | - structcheck 52 | - stylecheck 53 | - exhaustive 54 | 55 | linters-settings: 56 | govet: 57 | check-shadowing: true 58 | use-installed-packages: true 59 | dupl: 60 | threshold: 100 61 | goconst: 62 | min-len: 8 63 | min-occurrences: 3 64 | gocyclo: 65 | min-complexity: 20 66 | gocritic: 67 | disabled-checks: 68 | - ifElseChain 69 | 70 | 71 | issues: 72 | max-per-linter: 0 73 | max-same: 0 74 | exclude-use-default: false 75 | exclude: 76 | # Captured by errcheck. 77 | - '^(G104|G204):' 78 | # Very commonly not checked. 79 | - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*Print(f|ln|)|os\.(Un)?Setenv). is not checked' 80 | # Weird error only seen on Kochiku... 81 | - 'internal error: no range for' 82 | - 'exported method `.*\.(MarshalJSON|UnmarshalJSON|URN|Payload|GoString|Close|Provides|Requires|ExcludeFromHash|MarshalText|UnmarshalText|Description|Check|Poll|Severity)` should have comment or be unexported' 83 | - 'composite literal uses unkeyed fields' 84 | - 'declaration of "err" shadows declaration' 85 | - 'by other packages, and that stutters' 86 | - 'Potential file inclusion via variable' 87 | - 'at least one file in a package should have a package comment' 88 | - 'bad syntax for struct tag pair' 89 | -------------------------------------------------------------------------------- /comment_extractor.go: -------------------------------------------------------------------------------- 1 | package jsonschema 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | gopath "path" 7 | "path/filepath" 8 | "strings" 9 | 10 | "go/ast" 11 | "go/doc" 12 | "go/parser" 13 | "go/token" 14 | ) 15 | 16 | // ExtractGoComments will read all the go files contained in the provided path, 17 | // including sub-directories, in order to generate a dictionary of comments 18 | // associated with Types and Fields. The results will be added to the `commentsMap` 19 | // provided in the parameters and expected to be used for Schema "description" fields. 20 | // 21 | // The `go/parser` library is used to extract all the comments and unfortunately doesn't 22 | // have a built-in way to determine the fully qualified name of a package. The `base` paremeter, 23 | // the URL used to import that package, is thus required to be able to match reflected types. 24 | // 25 | // When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase 26 | // only. Field comments, which tend to be much shorter, will include everything. 27 | func ExtractGoComments(base, path string, commentMap map[string]string) error { 28 | fset := token.NewFileSet() 29 | dict := make(map[string][]*ast.Package) 30 | err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { 31 | if err != nil { 32 | return err 33 | } 34 | if info.IsDir() { 35 | d, err := parser.ParseDir(fset, path, nil, parser.ParseComments) 36 | if err != nil { 37 | return err 38 | } 39 | for _, v := range d { 40 | // paths may have multiple packages, like for tests 41 | k := gopath.Join(base, path) 42 | dict[k] = append(dict[k], v) 43 | } 44 | } 45 | return nil 46 | }) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | for pkg, p := range dict { 52 | for _, f := range p { 53 | gtxt := "" 54 | typ := "" 55 | ast.Inspect(f, func(n ast.Node) bool { 56 | switch x := n.(type) { 57 | case *ast.TypeSpec: 58 | typ = x.Name.String() 59 | if !ast.IsExported(typ) { 60 | typ = "" 61 | } else { 62 | txt := x.Doc.Text() 63 | if txt == "" && gtxt != "" { 64 | txt = gtxt 65 | gtxt = "" 66 | } 67 | txt = doc.Synopsis(txt) 68 | commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt) 69 | } 70 | case *ast.Field: 71 | txt := x.Doc.Text() 72 | if typ != "" && txt != "" { 73 | for _, n := range x.Names { 74 | if ast.IsExported(n.String()) { 75 | k := fmt.Sprintf("%s.%s.%s", pkg, typ, n) 76 | commentMap[k] = strings.TrimSpace(txt) 77 | } 78 | } 79 | } 80 | case *ast.GenDecl: 81 | // remember for the next type 82 | gtxt = x.Doc.Text() 83 | } 84 | return true 85 | }) 86 | } 87 | } 88 | 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /fixtures/go_comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/User", 4 | "definitions": { 5 | "Pet": { 6 | "required": [ 7 | "name" 8 | ], 9 | "properties": { 10 | "name": { 11 | "type": "string", 12 | "title": "Name", 13 | "description": "Name of the animal." 14 | } 15 | }, 16 | "additionalProperties": false, 17 | "type": "object", 18 | "description": "Pet defines the user's fury friend." 19 | }, 20 | "Plant": { 21 | "required": [ 22 | "variant" 23 | ], 24 | "properties": { 25 | "variant": { 26 | "type": "string", 27 | "title": "Variant" 28 | } 29 | }, 30 | "additionalProperties": false, 31 | "type": "object", 32 | "description": "Plant represents the plants the user might have and serves as a test of structs inside a `type` set." 33 | }, 34 | "User": { 35 | "required": [ 36 | "id", 37 | "name", 38 | "pets", 39 | "plants" 40 | ], 41 | "properties": { 42 | "id": { 43 | "type": "integer", 44 | "description": "Unique sequential identifier." 45 | }, 46 | "name": { 47 | "maxLength": 20, 48 | "minLength": 1, 49 | "pattern": ".*", 50 | "type": "string", 51 | "title": "the name", 52 | "description": "this is a property", 53 | "default": "alex", 54 | "examples": [ 55 | "joe", 56 | "lucy" 57 | ] 58 | }, 59 | "friends": { 60 | "items": { 61 | "type": "integer" 62 | }, 63 | "type": "array", 64 | "description": "list of IDs, omitted when empty" 65 | }, 66 | "tags": { 67 | "patternProperties": { 68 | ".*": { 69 | "additionalProperties": true 70 | } 71 | }, 72 | "type": "object" 73 | }, 74 | "pets": { 75 | "items": { 76 | "$schema": "http://json-schema.org/draft-04/schema#", 77 | "$ref": "#/definitions/Pet" 78 | }, 79 | "type": "array", 80 | "description": "An array of pets the user cares for." 81 | }, 82 | "plants": { 83 | "items": { 84 | "$schema": "http://json-schema.org/draft-04/schema#", 85 | "$ref": "#/definitions/Plant" 86 | }, 87 | "type": "array", 88 | "title": "Pants", 89 | "description": "Set of plants that the user likes" 90 | } 91 | }, 92 | "additionalProperties": false, 93 | "type": "object", 94 | "description": "User is used as a base to provide tests for comments." 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /fixtures/defaults_expanded_toplevel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 3 | "required": [ 4 | "some_base_property", 5 | "some_base_property_yaml", 6 | "grand", 7 | "SomeUntaggedBaseProperty", 8 | "PublicNonExported", 9 | "id", 10 | "name", 11 | "password", 12 | "TestFlag", 13 | "age", 14 | "email", 15 | "Baz", 16 | "color", 17 | "roles", 18 | "raw" 19 | ], 20 | "properties": { 21 | "some_base_property": { 22 | "type": "integer" 23 | }, 24 | "some_base_property_yaml": { 25 | "type": "integer" 26 | }, 27 | "grand": { 28 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 29 | "$ref": "#\/definitions\/GrandfatherType" 30 | }, 31 | "SomeUntaggedBaseProperty": { 32 | "type": "boolean" 33 | }, 34 | "PublicNonExported": { 35 | "type": "integer" 36 | }, 37 | "id": { 38 | "type": "integer" 39 | }, 40 | "name": { 41 | "maxLength": 20, 42 | "minLength": 1, 43 | "pattern": ".*", 44 | "type": "string", 45 | "title": "the name", 46 | "description": "this is a property", 47 | "default": "alex", 48 | "examples": [ 49 | "joe", 50 | "lucy" 51 | ], 52 | "readOnly": true 53 | }, 54 | "password": { 55 | "type": "string", 56 | "writeOnly": true 57 | }, 58 | "friends": { 59 | "items": { 60 | "type": "integer" 61 | }, 62 | "type": "array", 63 | "description": "list of IDs, omitted when empty" 64 | }, 65 | "tags": { 66 | "patternProperties": { 67 | ".*": { 68 | "additionalProperties": true 69 | } 70 | }, 71 | "type": "object" 72 | }, 73 | "TestFlag": { 74 | "type": "boolean" 75 | }, 76 | "birth_date": { 77 | "type": "string", 78 | "format": "date-time" 79 | }, 80 | "website": { 81 | "type": "string", 82 | "format": "uri" 83 | }, 84 | "network_address": { 85 | "type": "string", 86 | "format": "ipv4" 87 | }, 88 | "photo": { 89 | "type": "string", 90 | "media": { 91 | "binaryEncoding": "base64" 92 | } 93 | }, 94 | "photo2": { 95 | "type": "string", 96 | "media": { 97 | "binaryEncoding": "base64" 98 | } 99 | }, 100 | "feeling": { 101 | "oneOf": [ 102 | { 103 | "type": "string" 104 | }, 105 | { 106 | "type": "integer" 107 | } 108 | ] 109 | }, 110 | "age": { 111 | "maximum": 120, 112 | "exclusiveMaximum": true, 113 | "minimum": 18, 114 | "exclusiveMinimum": true, 115 | "type": "integer" 116 | }, 117 | "email": { 118 | "type": "string", 119 | "format": "email" 120 | }, 121 | "Baz": { 122 | "type": "string", 123 | "foo": [ 124 | "bar", 125 | "bar1" 126 | ], 127 | "hello": "world" 128 | }, 129 | "color": { 130 | "enum": [ 131 | "red", 132 | "green", 133 | "blue" 134 | ], 135 | "type": "string" 136 | }, 137 | "rank": { 138 | "enum": [ 139 | 1, 140 | 2, 141 | 3 142 | ], 143 | "type": "integer" 144 | }, 145 | "mult": { 146 | "enum": [ 147 | 1.0, 148 | 1.5, 149 | 2.0 150 | ], 151 | "type": "number" 152 | }, 153 | "roles": { 154 | "items": { 155 | "enum": [ 156 | "admin", 157 | "moderator", 158 | "user" 159 | ], 160 | "type": "string" 161 | }, 162 | "type": "array" 163 | }, 164 | "priorities": { 165 | "items": { 166 | "enum": [ 167 | -1, 168 | 0, 169 | 1 170 | ], 171 | "type": "integer" 172 | }, 173 | "type": "array" 174 | }, 175 | "offsets": { 176 | "items": { 177 | "enum": [ 178 | 1.570796, 179 | 3.141592, 180 | 6.283185 181 | ], 182 | "type": "number" 183 | }, 184 | "type": "array" 185 | }, 186 | "raw": { 187 | "additionalProperties": true 188 | } 189 | }, 190 | "additionalProperties": false, 191 | "type": "object", 192 | "definitions": { 193 | "GrandfatherType": { 194 | "required": [ 195 | "family_name" 196 | ], 197 | "properties": { 198 | "family_name": { 199 | "type": "string" 200 | } 201 | }, 202 | "additionalProperties": false, 203 | "type": "object" 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /fixtures/required_from_jsontags.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 3 | "$ref": "#\/definitions\/TestUser", 4 | "definitions": { 5 | "GrandfatherType": { 6 | "required": [ 7 | "family_name" 8 | ], 9 | "properties": { 10 | "family_name": { 11 | "type": "string" 12 | } 13 | }, 14 | "additionalProperties": false, 15 | "type": "object" 16 | }, 17 | "TestUser": { 18 | "required": [ 19 | "SomeUntaggedBaseProperty", 20 | "id", 21 | "name", 22 | "photo", 23 | "photo2" 24 | ], 25 | "properties": { 26 | "some_base_property": { 27 | "type": "integer" 28 | }, 29 | "some_base_property_yaml": { 30 | "type": "integer" 31 | }, 32 | "grand": { 33 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 34 | "$ref": "#\/definitions\/GrandfatherType" 35 | }, 36 | "SomeUntaggedBaseProperty": { 37 | "type": "boolean" 38 | }, 39 | "PublicNonExported": { 40 | "type": "integer" 41 | }, 42 | "id": { 43 | "type": "integer" 44 | }, 45 | "name": { 46 | "maxLength": 20, 47 | "minLength": 1, 48 | "pattern": ".*", 49 | "type": "string", 50 | "title": "the name", 51 | "description": "this is a property", 52 | "default": "alex", 53 | "examples": [ 54 | "joe", 55 | "lucy" 56 | ], 57 | "readOnly": true 58 | }, 59 | "password": { 60 | "type": "string", 61 | "writeOnly": true 62 | }, 63 | "friends": { 64 | "items": { 65 | "type": "integer" 66 | }, 67 | "type": "array", 68 | "description": "list of IDs, omitted when empty" 69 | }, 70 | "tags": { 71 | "patternProperties": { 72 | ".*": { 73 | "additionalProperties": true 74 | } 75 | }, 76 | "type": "object" 77 | }, 78 | "TestFlag": { 79 | "type": "boolean" 80 | }, 81 | "birth_date": { 82 | "type": "string", 83 | "format": "date-time" 84 | }, 85 | "website": { 86 | "type": "string", 87 | "format": "uri" 88 | }, 89 | "network_address": { 90 | "type": "string", 91 | "format": "ipv4" 92 | }, 93 | "photo": { 94 | "type": "string", 95 | "media": { 96 | "binaryEncoding": "base64" 97 | } 98 | }, 99 | "photo2": { 100 | "type": "string", 101 | "media": { 102 | "binaryEncoding": "base64" 103 | } 104 | }, 105 | "feeling": { 106 | "oneOf": [ 107 | { 108 | "type": "string" 109 | }, 110 | { 111 | "type": "integer" 112 | } 113 | ] 114 | }, 115 | "age": { 116 | "maximum": 120, 117 | "exclusiveMaximum": true, 118 | "minimum": 18, 119 | "exclusiveMinimum": true, 120 | "type": "integer" 121 | }, 122 | "email": { 123 | "type": "string", 124 | "format": "email" 125 | }, 126 | "Baz": { 127 | "type": "string", 128 | "foo": [ 129 | "bar", 130 | "bar1" 131 | ], 132 | "hello": "world" 133 | }, 134 | "color": { 135 | "enum": [ 136 | "red", 137 | "green", 138 | "blue" 139 | ], 140 | "type": "string" 141 | }, 142 | "rank": { 143 | "enum": [ 144 | 1, 145 | 2, 146 | 3 147 | ], 148 | "type": "integer" 149 | }, 150 | "mult": { 151 | "enum": [ 152 | 1, 153 | 1.5, 154 | 2 155 | ], 156 | "type": "number" 157 | }, 158 | "roles": { 159 | "items": { 160 | "enum": [ 161 | "admin", 162 | "moderator", 163 | "user" 164 | ], 165 | "type": "string" 166 | }, 167 | "type": "array" 168 | }, 169 | "priorities": { 170 | "items": { 171 | "enum": [ 172 | -1, 173 | 0, 174 | 1 175 | ], 176 | "type": "integer" 177 | }, 178 | "type": "array" 179 | }, 180 | "offsets": { 181 | "items": { 182 | "enum": [ 183 | 1.570796, 184 | 3.141592, 185 | 6.283185 186 | ], 187 | "type": "number" 188 | }, 189 | "type": "array" 190 | }, 191 | "raw": { 192 | "additionalProperties": true 193 | } 194 | }, 195 | "additionalProperties": false, 196 | "type": "object" 197 | } 198 | } 199 | } -------------------------------------------------------------------------------- /fixtures/ignore_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 3 | "$ref": "#\/definitions\/TestUser", 4 | "definitions": { 5 | "GrandfatherType": { 6 | "properties": {}, 7 | "additionalProperties": true, 8 | "type": "object" 9 | }, 10 | "TestUser": { 11 | "required": [ 12 | "some_base_property", 13 | "some_base_property_yaml", 14 | "grand", 15 | "SomeUntaggedBaseProperty", 16 | "PublicNonExported", 17 | "id", 18 | "name", 19 | "password", 20 | "TestFlag", 21 | "age", 22 | "email", 23 | "Baz", 24 | "color", 25 | "roles", 26 | "raw" 27 | ], 28 | "properties": { 29 | "some_base_property": { 30 | "type": "integer" 31 | }, 32 | "some_base_property_yaml": { 33 | "type": "integer" 34 | }, 35 | "grand": { 36 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 37 | "$ref": "#\/definitions\/GrandfatherType" 38 | }, 39 | "SomeUntaggedBaseProperty": { 40 | "type": "boolean" 41 | }, 42 | "PublicNonExported": { 43 | "type": "integer" 44 | }, 45 | "id": { 46 | "type": "integer" 47 | }, 48 | "name": { 49 | "maxLength": 20, 50 | "minLength": 1, 51 | "pattern": ".*", 52 | "type": "string", 53 | "title": "the name", 54 | "description": "this is a property", 55 | "default": "alex", 56 | "examples": [ 57 | "joe", 58 | "lucy" 59 | ], 60 | "readOnly": true 61 | }, 62 | "password": { 63 | "type": "string", 64 | "writeOnly": true 65 | }, 66 | "friends": { 67 | "items": { 68 | "type": "integer" 69 | }, 70 | "type": "array", 71 | "description": "list of IDs, omitted when empty" 72 | }, 73 | "tags": { 74 | "patternProperties": { 75 | ".*": { 76 | "additionalProperties": true 77 | } 78 | }, 79 | "type": "object" 80 | }, 81 | "TestFlag": { 82 | "type": "boolean" 83 | }, 84 | "birth_date": { 85 | "type": "string", 86 | "format": "date-time" 87 | }, 88 | "website": { 89 | "type": "string", 90 | "format": "uri" 91 | }, 92 | "network_address": { 93 | "type": "string", 94 | "format": "ipv4" 95 | }, 96 | "photo": { 97 | "type": "string", 98 | "media": { 99 | "binaryEncoding": "base64" 100 | } 101 | }, 102 | "photo2": { 103 | "type": "string", 104 | "media": { 105 | "binaryEncoding": "base64" 106 | } 107 | }, 108 | "feeling": { 109 | "oneOf": [ 110 | { 111 | "type": "string" 112 | }, 113 | { 114 | "type": "integer" 115 | } 116 | ] 117 | }, 118 | "age": { 119 | "maximum": 120, 120 | "exclusiveMaximum": true, 121 | "minimum": 18, 122 | "exclusiveMinimum": true, 123 | "type": "integer" 124 | }, 125 | "email": { 126 | "type": "string", 127 | "format": "email" 128 | }, 129 | "Baz": { 130 | "type": "string", 131 | "foo": [ 132 | "bar", 133 | "bar1" 134 | ], 135 | "hello": "world" 136 | }, 137 | "color": { 138 | "enum": [ 139 | "red", 140 | "green", 141 | "blue" 142 | ], 143 | "type": "string" 144 | }, 145 | "rank": { 146 | "enum": [ 147 | 1, 148 | 2, 149 | 3 150 | ], 151 | "type": "integer" 152 | }, 153 | "mult": { 154 | "enum": [ 155 | 1, 156 | 1.5, 157 | 2 158 | ], 159 | "type": "number" 160 | }, 161 | "roles": { 162 | "items": { 163 | "enum": [ 164 | "admin", 165 | "moderator", 166 | "user" 167 | ], 168 | "type": "string" 169 | }, 170 | "type": "array" 171 | }, 172 | "priorities": { 173 | "items": { 174 | "enum": [ 175 | -1, 176 | 0, 177 | 1 178 | ], 179 | "type": "integer" 180 | }, 181 | "type": "array" 182 | }, 183 | "offsets": { 184 | "items": { 185 | "enum": [ 186 | 1.570796, 187 | 3.141592, 188 | 6.283185 189 | ], 190 | "type": "number" 191 | }, 192 | "type": "array" 193 | }, 194 | "raw": { 195 | "additionalProperties": true 196 | } 197 | }, 198 | "additionalProperties": false, 199 | "type": "object" 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /fixtures/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 3 | "$ref": "#\/definitions\/TestUser", 4 | "definitions": { 5 | "GrandfatherType": { 6 | "required": [ 7 | "family_name" 8 | ], 9 | "properties": { 10 | "family_name": { 11 | "type": "string" 12 | } 13 | }, 14 | "additionalProperties": false, 15 | "type": "object" 16 | }, 17 | "TestUser": { 18 | "required": [ 19 | "some_base_property", 20 | "some_base_property_yaml", 21 | "grand", 22 | "SomeUntaggedBaseProperty", 23 | "PublicNonExported", 24 | "id", 25 | "name", 26 | "password", 27 | "TestFlag", 28 | "age", 29 | "email", 30 | "Baz", 31 | "color", 32 | "roles", 33 | "raw" 34 | ], 35 | "properties": { 36 | "some_base_property": { 37 | "type": "integer" 38 | }, 39 | "some_base_property_yaml": { 40 | "type": "integer" 41 | }, 42 | "grand": { 43 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 44 | "$ref": "#\/definitions\/GrandfatherType" 45 | }, 46 | "SomeUntaggedBaseProperty": { 47 | "type": "boolean" 48 | }, 49 | "PublicNonExported": { 50 | "type": "integer" 51 | }, 52 | "id": { 53 | "type": "integer" 54 | }, 55 | "name": { 56 | "maxLength": 20, 57 | "minLength": 1, 58 | "pattern": ".*", 59 | "type": "string", 60 | "title": "the name", 61 | "description": "this is a property", 62 | "default": "alex", 63 | "examples": [ 64 | "joe", 65 | "lucy" 66 | ], 67 | "readOnly": true 68 | }, 69 | "password": { 70 | "type": "string", 71 | "writeOnly": true 72 | }, 73 | "friends": { 74 | "items": { 75 | "type": "integer" 76 | }, 77 | "type": "array", 78 | "description": "list of IDs, omitted when empty" 79 | }, 80 | "tags": { 81 | "patternProperties": { 82 | ".*": { 83 | "additionalProperties": true 84 | } 85 | }, 86 | "type": "object" 87 | }, 88 | "TestFlag": { 89 | "type": "boolean" 90 | }, 91 | "birth_date": { 92 | "type": "string", 93 | "format": "date-time" 94 | }, 95 | "website": { 96 | "type": "string", 97 | "format": "uri" 98 | }, 99 | "network_address": { 100 | "type": "string", 101 | "format": "ipv4" 102 | }, 103 | "photo": { 104 | "type": "string", 105 | "media": { 106 | "binaryEncoding": "base64" 107 | } 108 | }, 109 | "photo2": { 110 | "type": "string", 111 | "media": { 112 | "binaryEncoding": "base64" 113 | } 114 | }, 115 | "feeling": { 116 | "oneOf": [ 117 | { 118 | "type": "string" 119 | }, 120 | { 121 | "type": "integer" 122 | } 123 | ] 124 | }, 125 | "age": { 126 | "maximum": 120, 127 | "exclusiveMaximum": true, 128 | "minimum": 18, 129 | "exclusiveMinimum": true, 130 | "type": "integer" 131 | }, 132 | "email": { 133 | "type": "string", 134 | "format": "email" 135 | }, 136 | "Baz": { 137 | "type": "string", 138 | "foo": [ 139 | "bar", 140 | "bar1" 141 | ], 142 | "hello": "world" 143 | }, 144 | "color": { 145 | "enum": [ 146 | "red", 147 | "green", 148 | "blue" 149 | ], 150 | "type": "string" 151 | }, 152 | "rank": { 153 | "enum": [ 154 | 1, 155 | 2, 156 | 3 157 | ], 158 | "type": "integer" 159 | }, 160 | "mult": { 161 | "enum": [ 162 | 1, 163 | 1.5, 164 | 2 165 | ], 166 | "type": "number" 167 | }, 168 | "roles": { 169 | "items": { 170 | "enum": [ 171 | "admin", 172 | "moderator", 173 | "user" 174 | ], 175 | "type": "string" 176 | }, 177 | "type": "array" 178 | }, 179 | "priorities": { 180 | "items": { 181 | "enum": [ 182 | -1, 183 | 0, 184 | 1 185 | ], 186 | "type": "integer" 187 | }, 188 | "type": "array" 189 | }, 190 | "offsets": { 191 | "items": { 192 | "enum": [ 193 | 1.570796, 194 | 3.141592, 195 | 6.283185 196 | ], 197 | "type": "number" 198 | }, 199 | "type": "array" 200 | }, 201 | "raw": { 202 | "additionalProperties": true 203 | } 204 | }, 205 | "additionalProperties": false, 206 | "type": "object" 207 | } 208 | } 209 | } -------------------------------------------------------------------------------- /fixtures/allow_additional_props.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 3 | "$ref": "#\/definitions\/TestUser", 4 | "definitions": { 5 | "GrandfatherType": { 6 | "required": [ 7 | "family_name" 8 | ], 9 | "properties": { 10 | "family_name": { 11 | "type": "string" 12 | } 13 | }, 14 | "additionalProperties": true, 15 | "type": "object" 16 | }, 17 | "TestUser": { 18 | "required": [ 19 | "some_base_property", 20 | "some_base_property_yaml", 21 | "grand", 22 | "SomeUntaggedBaseProperty", 23 | "PublicNonExported", 24 | "id", 25 | "name", 26 | "password", 27 | "TestFlag", 28 | "age", 29 | "email", 30 | "Baz", 31 | "color", 32 | "roles", 33 | "raw" 34 | ], 35 | "properties": { 36 | "some_base_property": { 37 | "type": "integer" 38 | }, 39 | "some_base_property_yaml": { 40 | "type": "integer" 41 | }, 42 | "grand": { 43 | "$schema": "http:\/\/json-schema.org\/draft-04\/schema#", 44 | "$ref": "#\/definitions\/GrandfatherType" 45 | }, 46 | "SomeUntaggedBaseProperty": { 47 | "type": "boolean" 48 | }, 49 | "PublicNonExported": { 50 | "type": "integer" 51 | }, 52 | "id": { 53 | "type": "integer" 54 | }, 55 | "name": { 56 | "maxLength": 20, 57 | "minLength": 1, 58 | "pattern": ".*", 59 | "type": "string", 60 | "title": "the name", 61 | "description": "this is a property", 62 | "default": "alex", 63 | "examples": [ 64 | "joe", 65 | "lucy" 66 | ], 67 | "readOnly": true 68 | }, 69 | "password": { 70 | "type": "string", 71 | "writeOnly": true 72 | }, 73 | "friends": { 74 | "items": { 75 | "type": "integer" 76 | }, 77 | "type": "array", 78 | "description": "list of IDs, omitted when empty" 79 | }, 80 | "tags": { 81 | "patternProperties": { 82 | ".*": { 83 | "additionalProperties": true 84 | } 85 | }, 86 | "type": "object" 87 | }, 88 | "TestFlag": { 89 | "type": "boolean" 90 | }, 91 | "birth_date": { 92 | "type": "string", 93 | "format": "date-time" 94 | }, 95 | "website": { 96 | "type": "string", 97 | "format": "uri" 98 | }, 99 | "network_address": { 100 | "type": "string", 101 | "format": "ipv4" 102 | }, 103 | "photo": { 104 | "type": "string", 105 | "media": { 106 | "binaryEncoding": "base64" 107 | } 108 | }, 109 | "photo2": { 110 | "type": "string", 111 | "media": { 112 | "binaryEncoding": "base64" 113 | } 114 | }, 115 | "feeling": { 116 | "oneOf": [ 117 | { 118 | "type": "string" 119 | }, 120 | { 121 | "type": "integer" 122 | } 123 | ] 124 | }, 125 | "age": { 126 | "maximum": 120, 127 | "exclusiveMaximum": true, 128 | "minimum": 18, 129 | "exclusiveMinimum": true, 130 | "type": "integer" 131 | }, 132 | "email": { 133 | "type": "string", 134 | "format": "email" 135 | }, 136 | "Baz": { 137 | "type": "string", 138 | "foo": [ 139 | "bar", 140 | "bar1" 141 | ], 142 | "hello": "world" 143 | }, 144 | "color": { 145 | "enum": [ 146 | "red", 147 | "green", 148 | "blue" 149 | ], 150 | "type": "string" 151 | }, 152 | "rank": { 153 | "enum": [ 154 | 1, 155 | 2, 156 | 3 157 | ], 158 | "type": "integer" 159 | }, 160 | "mult": { 161 | "enum": [ 162 | 1, 163 | 1.5, 164 | 2 165 | ], 166 | "type": "number" 167 | }, 168 | "roles": { 169 | "items": { 170 | "enum": [ 171 | "admin", 172 | "moderator", 173 | "user" 174 | ], 175 | "type": "string" 176 | }, 177 | "type": "array" 178 | }, 179 | "priorities": { 180 | "items": { 181 | "enum": [ 182 | -1, 183 | 0, 184 | 1 185 | ], 186 | "type": "integer" 187 | }, 188 | "type": "array" 189 | }, 190 | "offsets": { 191 | "items": { 192 | "enum": [ 193 | 1.570796, 194 | 3.141592, 195 | 6.283185 196 | ], 197 | "type": "number" 198 | }, 199 | "type": "array" 200 | }, 201 | "raw": { 202 | "additionalProperties": true 203 | } 204 | }, 205 | "additionalProperties": true, 206 | "type": "object" 207 | } 208 | } 209 | } -------------------------------------------------------------------------------- /fixtures/fully_qualified.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "$ref": "#/definitions/github.com/alecthomas/jsonschema.TestUser", 4 | "definitions": { 5 | "github.com/alecthomas/jsonschema.GrandfatherType": { 6 | "required": [ 7 | "family_name" 8 | ], 9 | "properties": { 10 | "family_name": { 11 | "type": "string" 12 | } 13 | }, 14 | "additionalProperties": false, 15 | "type": "object" 16 | }, 17 | "github.com/alecthomas/jsonschema.TestUser": { 18 | "required": [ 19 | "some_base_property", 20 | "some_base_property_yaml", 21 | "grand", 22 | "SomeUntaggedBaseProperty", 23 | "PublicNonExported", 24 | "id", 25 | "name", 26 | "password", 27 | "TestFlag", 28 | "age", 29 | "email", 30 | "Baz", 31 | "color", 32 | "roles", 33 | "raw" 34 | ], 35 | "properties": { 36 | "some_base_property": { 37 | "type": "integer" 38 | }, 39 | "some_base_property_yaml": { 40 | "type": "integer" 41 | }, 42 | "grand": { 43 | "$schema": "http://json-schema.org/draft-04/schema#", 44 | "$ref": "#/definitions/github.com/alecthomas/jsonschema.GrandfatherType" 45 | }, 46 | "SomeUntaggedBaseProperty": { 47 | "type": "boolean" 48 | }, 49 | "PublicNonExported": { 50 | "type": "integer" 51 | }, 52 | "id": { 53 | "type": "integer" 54 | }, 55 | "name": { 56 | "maxLength": 20, 57 | "minLength": 1, 58 | "pattern": ".*", 59 | "type": "string", 60 | "title": "the name", 61 | "description": "this is a property", 62 | "default": "alex", 63 | "examples": [ 64 | "joe", 65 | "lucy" 66 | ], 67 | "readOnly": true 68 | }, 69 | "password": { 70 | "type": "string", 71 | "writeOnly": true 72 | }, 73 | "friends": { 74 | "items": { 75 | "type": "integer" 76 | }, 77 | "type": "array", 78 | "description": "list of IDs, omitted when empty" 79 | }, 80 | "tags": { 81 | "patternProperties": { 82 | ".*": { 83 | "additionalProperties": true 84 | } 85 | }, 86 | "type": "object" 87 | }, 88 | "TestFlag": { 89 | "type": "boolean" 90 | }, 91 | "birth_date": { 92 | "type": "string", 93 | "format": "date-time" 94 | }, 95 | "website": { 96 | "type": "string", 97 | "format": "uri" 98 | }, 99 | "network_address": { 100 | "type": "string", 101 | "format": "ipv4" 102 | }, 103 | "photo": { 104 | "type": "string", 105 | "media": { 106 | "binaryEncoding": "base64" 107 | } 108 | }, 109 | "photo2": { 110 | "type": "string", 111 | "media": { 112 | "binaryEncoding": "base64" 113 | } 114 | }, 115 | "feeling": { 116 | "oneOf": [ 117 | { 118 | "type": "string" 119 | }, 120 | { 121 | "type": "integer" 122 | } 123 | ] 124 | }, 125 | "age": { 126 | "maximum": 120, 127 | "exclusiveMaximum": true, 128 | "minimum": 18, 129 | "exclusiveMinimum": true, 130 | "type": "integer" 131 | }, 132 | "email": { 133 | "type": "string", 134 | "format": "email" 135 | }, 136 | "Baz": { 137 | "type": "string", 138 | "foo": [ 139 | "bar", 140 | "bar1" 141 | ], 142 | "hello": "world" 143 | }, 144 | "color": { 145 | "enum": [ 146 | "red", 147 | "green", 148 | "blue" 149 | ], 150 | "type": "string" 151 | }, 152 | "rank": { 153 | "enum": [ 154 | 1, 155 | 2, 156 | 3 157 | ], 158 | "type": "integer" 159 | }, 160 | "mult": { 161 | "enum": [ 162 | 1, 163 | 1.5, 164 | 2 165 | ], 166 | "type": "number" 167 | }, 168 | "roles": { 169 | "items": { 170 | "enum": [ 171 | "admin", 172 | "moderator", 173 | "user" 174 | ], 175 | "type": "string" 176 | }, 177 | "type": "array" 178 | }, 179 | "priorities": { 180 | "items": { 181 | "enum": [ 182 | -1, 183 | 0, 184 | 1 185 | ], 186 | "type": "integer" 187 | }, 188 | "type": "array" 189 | }, 190 | "offsets": { 191 | "items": { 192 | "enum": [ 193 | 1.570796, 194 | 3.141592, 195 | 6.283185 196 | ], 197 | "type": "number" 198 | }, 199 | "type": "array" 200 | }, 201 | "raw": { 202 | "additionalProperties": true 203 | } 204 | }, 205 | "additionalProperties": false, 206 | "type": "object" 207 | } 208 | } 209 | } -------------------------------------------------------------------------------- /fixtures/no_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": [ 3 | "some_base_property", 4 | "some_base_property_yaml", 5 | "grand", 6 | "SomeUntaggedBaseProperty", 7 | "PublicNonExported", 8 | "id", 9 | "name", 10 | "password", 11 | "TestFlag", 12 | "age", 13 | "email", 14 | "Baz", 15 | "color", 16 | "roles", 17 | "raw" 18 | ], 19 | "properties": { 20 | "some_base_property": { 21 | "type": "integer" 22 | }, 23 | "some_base_property_yaml": { 24 | "type": "integer" 25 | }, 26 | "grand": { 27 | "required": [ 28 | "family_name" 29 | ], 30 | "properties": { 31 | "family_name": { 32 | "type": "string" 33 | } 34 | }, 35 | "additionalProperties": false, 36 | "type": "object" 37 | }, 38 | "SomeUntaggedBaseProperty": { 39 | "type": "boolean" 40 | }, 41 | "PublicNonExported": { 42 | "type": "integer" 43 | }, 44 | "id": { 45 | "type": "integer" 46 | }, 47 | "name": { 48 | "maxLength": 20, 49 | "minLength": 1, 50 | "pattern": ".*", 51 | "type": "string", 52 | "title": "the name", 53 | "description": "this is a property", 54 | "default": "alex", 55 | "examples": [ 56 | "joe", 57 | "lucy" 58 | ], 59 | "readOnly": true 60 | }, 61 | "password": { 62 | "type": "string", 63 | "writeOnly": true 64 | }, 65 | "friends": { 66 | "items": { 67 | "type": "integer" 68 | }, 69 | "type": "array", 70 | "description": "list of IDs, omitted when empty" 71 | }, 72 | "tags": { 73 | "patternProperties": { 74 | ".*": { 75 | "additionalProperties": true 76 | } 77 | }, 78 | "type": "object" 79 | }, 80 | "TestFlag": { 81 | "type": "boolean" 82 | }, 83 | "birth_date": { 84 | "type": "string", 85 | "format": "date-time" 86 | }, 87 | "website": { 88 | "type": "string", 89 | "format": "uri" 90 | }, 91 | "network_address": { 92 | "type": "string", 93 | "format": "ipv4" 94 | }, 95 | "photo": { 96 | "type": "string", 97 | "media": { 98 | "binaryEncoding": "base64" 99 | } 100 | }, 101 | "photo2": { 102 | "type": "string", 103 | "media": { 104 | "binaryEncoding": "base64" 105 | } 106 | }, 107 | "feeling": { 108 | "oneOf": [ 109 | { 110 | "type": "string" 111 | }, 112 | { 113 | "type": "integer" 114 | } 115 | ] 116 | }, 117 | "age": { 118 | "maximum": 120, 119 | "exclusiveMaximum": true, 120 | "minimum": 18, 121 | "exclusiveMinimum": true, 122 | "type": "integer" 123 | }, 124 | "email": { 125 | "type": "string", 126 | "format": "email" 127 | }, 128 | "Baz": { 129 | "type": "string", 130 | "foo": [ 131 | "bar", 132 | "bar1" 133 | ], 134 | "hello": "world" 135 | }, 136 | "color": { 137 | "enum": [ 138 | "red", 139 | "green", 140 | "blue" 141 | ], 142 | "type": "string" 143 | }, 144 | "rank": { 145 | "enum": [ 146 | 1, 147 | 2, 148 | 3 149 | ], 150 | "type": "integer" 151 | }, 152 | "mult": { 153 | "enum": [ 154 | 1, 155 | 1.5, 156 | 2 157 | ], 158 | "type": "number" 159 | }, 160 | "roles": { 161 | "items": { 162 | "enum": [ 163 | "admin", 164 | "moderator", 165 | "user" 166 | ], 167 | "type": "string" 168 | }, 169 | "type": "array" 170 | }, 171 | "priorities": { 172 | "items": { 173 | "enum": [ 174 | -1, 175 | 0, 176 | 1 177 | ], 178 | "type": "integer" 179 | }, 180 | "type": "array" 181 | }, 182 | "offsets": { 183 | "items": { 184 | "enum": [ 185 | 1.570796, 186 | 3.141592, 187 | 6.283185 188 | ], 189 | "type": "number" 190 | }, 191 | "type": "array" 192 | }, 193 | "raw": { 194 | "additionalProperties": true 195 | } 196 | }, 197 | "additionalProperties": false, 198 | "type": "object", 199 | "definitions": { 200 | "GrandfatherType": { 201 | "required": [ 202 | "family_name" 203 | ], 204 | "properties": { 205 | "family_name": { 206 | "type": "string" 207 | } 208 | }, 209 | "additionalProperties": false, 210 | "type": "object" 211 | }, 212 | "TestUser": { 213 | "required": [ 214 | "some_base_property", 215 | "some_base_property_yaml", 216 | "grand", 217 | "SomeUntaggedBaseProperty", 218 | "PublicNonExported", 219 | "id", 220 | "name", 221 | "password", 222 | "TestFlag", 223 | "age", 224 | "email", 225 | "Baz", 226 | "color", 227 | "roles", 228 | "raw" 229 | ], 230 | "properties": { 231 | "some_base_property": { 232 | "type": "integer" 233 | }, 234 | "some_base_property_yaml": { 235 | "type": "integer" 236 | }, 237 | "grand": { 238 | "required": [ 239 | "family_name" 240 | ], 241 | "properties": { 242 | "family_name": { 243 | "type": "string" 244 | } 245 | }, 246 | "additionalProperties": false, 247 | "type": "object" 248 | }, 249 | "SomeUntaggedBaseProperty": { 250 | "type": "boolean" 251 | }, 252 | "PublicNonExported": { 253 | "type": "integer" 254 | }, 255 | "id": { 256 | "type": "integer" 257 | }, 258 | "name": { 259 | "maxLength": 20, 260 | "minLength": 1, 261 | "pattern": ".*", 262 | "type": "string", 263 | "title": "the name", 264 | "description": "this is a property", 265 | "default": "alex", 266 | "examples": [ 267 | "joe", 268 | "lucy" 269 | ], 270 | "readOnly": true 271 | }, 272 | "password": { 273 | "type": "string", 274 | "writeOnly": true 275 | }, 276 | "friends": { 277 | "items": { 278 | "type": "integer" 279 | }, 280 | "type": "array", 281 | "description": "list of IDs, omitted when empty" 282 | }, 283 | "tags": { 284 | "patternProperties": { 285 | ".*": { 286 | "additionalProperties": true 287 | } 288 | }, 289 | "type": "object" 290 | }, 291 | "TestFlag": { 292 | "type": "boolean" 293 | }, 294 | "birth_date": { 295 | "type": "string", 296 | "format": "date-time" 297 | }, 298 | "website": { 299 | "type": "string", 300 | "format": "uri" 301 | }, 302 | "network_address": { 303 | "type": "string", 304 | "format": "ipv4" 305 | }, 306 | "photo": { 307 | "type": "string", 308 | "media": { 309 | "binaryEncoding": "base64" 310 | } 311 | }, 312 | "photo2": { 313 | "type": "string", 314 | "media": { 315 | "binaryEncoding": "base64" 316 | } 317 | }, 318 | "feeling": { 319 | "oneOf": [ 320 | { 321 | "type": "string" 322 | }, 323 | { 324 | "type": "integer" 325 | } 326 | ] 327 | }, 328 | "age": { 329 | "maximum": 120, 330 | "exclusiveMaximum": true, 331 | "minimum": 18, 332 | "exclusiveMinimum": true, 333 | "type": "integer" 334 | }, 335 | "email": { 336 | "type": "string", 337 | "format": "email" 338 | }, 339 | "Baz": { 340 | "type": "string", 341 | "foo": [ 342 | "bar", 343 | "bar1" 344 | ], 345 | "hello": "world" 346 | }, 347 | "color": { 348 | "enum": [ 349 | "red", 350 | "green", 351 | "blue" 352 | ], 353 | "type": "string" 354 | }, 355 | "rank": { 356 | "enum": [ 357 | 1, 358 | 2, 359 | 3 360 | ], 361 | "type": "integer" 362 | }, 363 | "mult": { 364 | "enum": [ 365 | 1, 366 | 1.5, 367 | 2 368 | ], 369 | "type": "number" 370 | }, 371 | "roles": { 372 | "items": { 373 | "enum": [ 374 | "admin", 375 | "moderator", 376 | "user" 377 | ], 378 | "type": "string" 379 | }, 380 | "type": "array" 381 | }, 382 | "priorities": { 383 | "items": { 384 | "enum": [ 385 | -1, 386 | 0, 387 | 1 388 | ], 389 | "type": "integer" 390 | }, 391 | "type": "array" 392 | }, 393 | "offsets": { 394 | "items": { 395 | "enum": [ 396 | 1.570796, 397 | 3.141592, 398 | 6.283185 399 | ], 400 | "type": "number" 401 | }, 402 | "type": "array" 403 | }, 404 | "raw": { 405 | "additionalProperties": true 406 | } 407 | }, 408 | "additionalProperties": false, 409 | "type": "object" 410 | } 411 | } 412 | } -------------------------------------------------------------------------------- /fixtures/no_ref_qual_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": [ 3 | "some_base_property", 4 | "some_base_property_yaml", 5 | "grand", 6 | "SomeUntaggedBaseProperty", 7 | "PublicNonExported", 8 | "id", 9 | "name", 10 | "password", 11 | "TestFlag", 12 | "age", 13 | "email", 14 | "Baz", 15 | "color", 16 | "roles", 17 | "raw" 18 | ], 19 | "properties": { 20 | "some_base_property": { 21 | "type": "integer" 22 | }, 23 | "some_base_property_yaml": { 24 | "type": "integer" 25 | }, 26 | "grand": { 27 | "required": [ 28 | "family_name" 29 | ], 30 | "properties": { 31 | "family_name": { 32 | "type": "string" 33 | } 34 | }, 35 | "additionalProperties": false, 36 | "type": "object" 37 | }, 38 | "SomeUntaggedBaseProperty": { 39 | "type": "boolean" 40 | }, 41 | "PublicNonExported": { 42 | "type": "integer" 43 | }, 44 | "id": { 45 | "type": "integer" 46 | }, 47 | "name": { 48 | "maxLength": 20, 49 | "minLength": 1, 50 | "pattern": ".*", 51 | "type": "string", 52 | "title": "the name", 53 | "description": "this is a property", 54 | "default": "alex", 55 | "examples": [ 56 | "joe", 57 | "lucy" 58 | ], 59 | "readOnly": true 60 | }, 61 | "password": { 62 | "type": "string", 63 | "writeOnly": true 64 | }, 65 | "friends": { 66 | "items": { 67 | "type": "integer" 68 | }, 69 | "type": "array", 70 | "description": "list of IDs, omitted when empty" 71 | }, 72 | "tags": { 73 | "patternProperties": { 74 | ".*": { 75 | "additionalProperties": true 76 | } 77 | }, 78 | "type": "object" 79 | }, 80 | "TestFlag": { 81 | "type": "boolean" 82 | }, 83 | "birth_date": { 84 | "type": "string", 85 | "format": "date-time" 86 | }, 87 | "website": { 88 | "type": "string", 89 | "format": "uri" 90 | }, 91 | "network_address": { 92 | "type": "string", 93 | "format": "ipv4" 94 | }, 95 | "photo": { 96 | "type": "string", 97 | "media": { 98 | "binaryEncoding": "base64" 99 | } 100 | }, 101 | "photo2": { 102 | "type": "string", 103 | "media": { 104 | "binaryEncoding": "base64" 105 | } 106 | }, 107 | "feeling": { 108 | "oneOf": [ 109 | { 110 | "type": "string" 111 | }, 112 | { 113 | "type": "integer" 114 | } 115 | ] 116 | }, 117 | "age": { 118 | "maximum": 120, 119 | "exclusiveMaximum": true, 120 | "minimum": 18, 121 | "exclusiveMinimum": true, 122 | "type": "integer" 123 | }, 124 | "email": { 125 | "type": "string", 126 | "format": "email" 127 | }, 128 | "Baz": { 129 | "type": "string", 130 | "foo": [ 131 | "bar", 132 | "bar1" 133 | ], 134 | "hello": "world" 135 | }, 136 | "color": { 137 | "enum": [ 138 | "red", 139 | "green", 140 | "blue" 141 | ], 142 | "type": "string" 143 | }, 144 | "rank": { 145 | "enum": [ 146 | 1, 147 | 2, 148 | 3 149 | ], 150 | "type": "integer" 151 | }, 152 | "mult": { 153 | "enum": [ 154 | 1, 155 | 1.5, 156 | 2 157 | ], 158 | "type": "number" 159 | }, 160 | "roles": { 161 | "items": { 162 | "enum": [ 163 | "admin", 164 | "moderator", 165 | "user" 166 | ], 167 | "type": "string" 168 | }, 169 | "type": "array" 170 | }, 171 | "priorities": { 172 | "items": { 173 | "enum": [ 174 | -1, 175 | 0, 176 | 1 177 | ], 178 | "type": "integer" 179 | }, 180 | "type": "array" 181 | }, 182 | "offsets": { 183 | "items": { 184 | "enum": [ 185 | 1.570796, 186 | 3.141592, 187 | 6.283185 188 | ], 189 | "type": "number" 190 | }, 191 | "type": "array" 192 | }, 193 | "raw": { 194 | "additionalProperties": true 195 | } 196 | }, 197 | "additionalProperties": false, 198 | "type": "object", 199 | "definitions": { 200 | "github.com/alecthomas/jsonschema.GrandfatherType": { 201 | "required": [ 202 | "family_name" 203 | ], 204 | "properties": { 205 | "family_name": { 206 | "type": "string" 207 | } 208 | }, 209 | "additionalProperties": false, 210 | "type": "object" 211 | }, 212 | "github.com/alecthomas/jsonschema.TestUser": { 213 | "required": [ 214 | "some_base_property", 215 | "some_base_property_yaml", 216 | "grand", 217 | "SomeUntaggedBaseProperty", 218 | "PublicNonExported", 219 | "id", 220 | "name", 221 | "password", 222 | "TestFlag", 223 | "age", 224 | "email", 225 | "Baz", 226 | "color", 227 | "roles", 228 | "raw" 229 | ], 230 | "properties": { 231 | "some_base_property": { 232 | "type": "integer" 233 | }, 234 | "some_base_property_yaml": { 235 | "type": "integer" 236 | }, 237 | "grand": { 238 | "required": [ 239 | "family_name" 240 | ], 241 | "properties": { 242 | "family_name": { 243 | "type": "string" 244 | } 245 | }, 246 | "additionalProperties": false, 247 | "type": "object" 248 | }, 249 | "SomeUntaggedBaseProperty": { 250 | "type": "boolean" 251 | }, 252 | "PublicNonExported": { 253 | "type": "integer" 254 | }, 255 | "id": { 256 | "type": "integer" 257 | }, 258 | "name": { 259 | "maxLength": 20, 260 | "minLength": 1, 261 | "pattern": ".*", 262 | "type": "string", 263 | "title": "the name", 264 | "description": "this is a property", 265 | "default": "alex", 266 | "examples": [ 267 | "joe", 268 | "lucy" 269 | ], 270 | "readOnly": true 271 | }, 272 | "password": { 273 | "type": "string", 274 | "writeOnly": true 275 | }, 276 | "friends": { 277 | "items": { 278 | "type": "integer" 279 | }, 280 | "type": "array", 281 | "description": "list of IDs, omitted when empty" 282 | }, 283 | "tags": { 284 | "patternProperties": { 285 | ".*": { 286 | "additionalProperties": true 287 | } 288 | }, 289 | "type": "object" 290 | }, 291 | "TestFlag": { 292 | "type": "boolean" 293 | }, 294 | "birth_date": { 295 | "type": "string", 296 | "format": "date-time" 297 | }, 298 | "website": { 299 | "type": "string", 300 | "format": "uri" 301 | }, 302 | "network_address": { 303 | "type": "string", 304 | "format": "ipv4" 305 | }, 306 | "photo": { 307 | "type": "string", 308 | "media": { 309 | "binaryEncoding": "base64" 310 | } 311 | }, 312 | "photo2": { 313 | "type": "string", 314 | "media": { 315 | "binaryEncoding": "base64" 316 | } 317 | }, 318 | "feeling": { 319 | "oneOf": [ 320 | { 321 | "type": "string" 322 | }, 323 | { 324 | "type": "integer" 325 | } 326 | ] 327 | }, 328 | "age": { 329 | "maximum": 120, 330 | "exclusiveMaximum": true, 331 | "minimum": 18, 332 | "exclusiveMinimum": true, 333 | "type": "integer" 334 | }, 335 | "email": { 336 | "type": "string", 337 | "format": "email" 338 | }, 339 | "Baz": { 340 | "type": "string", 341 | "foo": [ 342 | "bar", 343 | "bar1" 344 | ], 345 | "hello": "world" 346 | }, 347 | "color": { 348 | "enum": [ 349 | "red", 350 | "green", 351 | "blue" 352 | ], 353 | "type": "string" 354 | }, 355 | "rank": { 356 | "enum": [ 357 | 1, 358 | 2, 359 | 3 360 | ], 361 | "type": "integer" 362 | }, 363 | "mult": { 364 | "enum": [ 365 | 1, 366 | 1.5, 367 | 2 368 | ], 369 | "type": "number" 370 | }, 371 | "roles": { 372 | "items": { 373 | "enum": [ 374 | "admin", 375 | "moderator", 376 | "user" 377 | ], 378 | "type": "string" 379 | }, 380 | "type": "array" 381 | }, 382 | "priorities": { 383 | "items": { 384 | "enum": [ 385 | -1, 386 | 0, 387 | 1 388 | ], 389 | "type": "integer" 390 | }, 391 | "type": "array" 392 | }, 393 | "offsets": { 394 | "items": { 395 | "enum": [ 396 | 1.570796, 397 | 3.141592, 398 | 6.283185 399 | ], 400 | "type": "number" 401 | }, 402 | "type": "array" 403 | }, 404 | "raw": { 405 | "additionalProperties": true 406 | } 407 | }, 408 | "additionalProperties": false, 409 | "type": "object" 410 | } 411 | } 412 | } -------------------------------------------------------------------------------- /reflect_test.go: -------------------------------------------------------------------------------- 1 | package jsonschema 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net" 7 | "net/url" 8 | "path/filepath" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | "time" 13 | 14 | "github.com/iancoleman/orderedmap" 15 | 16 | "github.com/alecthomas/jsonschema/examples" 17 | 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | type GrandfatherType struct { 22 | FamilyName string `json:"family_name" jsonschema:"required"` 23 | } 24 | 25 | type SomeBaseType struct { 26 | SomeBaseProperty int `json:"some_base_property"` 27 | SomeBasePropertyYaml int `yaml:"some_base_property_yaml"` 28 | // The jsonschema required tag is nonsensical for private and ignored properties. 29 | // Their presence here tests that the fields *will not* be required in the output 30 | // schema, even if they are tagged required. 31 | somePrivateBaseProperty string `jsonschema:"required"` 32 | SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` 33 | SomeSchemaIgnoredProperty string `jsonschema:"-,required"` 34 | Grandfather GrandfatherType `json:"grand"` 35 | 36 | SomeUntaggedBaseProperty bool `jsonschema:"required"` 37 | someUnexportedUntaggedBaseProperty bool 38 | } 39 | 40 | type MapType map[string]interface{} 41 | 42 | type nonExported struct { 43 | PublicNonExported int 44 | privateNonExported int 45 | } 46 | 47 | type ProtoEnum int32 48 | 49 | func (ProtoEnum) EnumDescriptor() ([]byte, []int) { return []byte(nil), []int{0} } 50 | 51 | const ( 52 | Unset ProtoEnum = iota 53 | Great 54 | ) 55 | 56 | type TestUser struct { 57 | SomeBaseType 58 | nonExported 59 | MapType 60 | 61 | ID int `json:"id" jsonschema:"required"` 62 | Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex,readOnly=true"` 63 | Password string `json:"password" jsonschema:"writeOnly=true"` 64 | Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` 65 | Tags map[string]interface{} `json:"tags,omitempty"` 66 | 67 | TestFlag bool 68 | IgnoredCounter int `json:"-"` 69 | 70 | // Tests for RFC draft-wright-json-schema-validation-00, section 7.3 71 | BirthDate time.Time `json:"birth_date,omitempty"` 72 | Website url.URL `json:"website,omitempty"` 73 | IPAddress net.IP `json:"network_address,omitempty"` 74 | 75 | // Tests for RFC draft-wright-json-schema-hyperschema-00, section 4 76 | Photo []byte `json:"photo,omitempty" jsonschema:"required"` 77 | Photo2 Bytes `json:"photo2,omitempty" jsonschema:"required"` 78 | 79 | // Tests for jsonpb enum support 80 | Feeling ProtoEnum `json:"feeling,omitempty"` 81 | Age int `json:"age" jsonschema:"minimum=18,maximum=120,exclusiveMaximum=true,exclusiveMinimum=true"` 82 | Email string `json:"email" jsonschema:"format=email"` 83 | 84 | // Test for "extras" support 85 | Baz string `jsonschema_extras:"foo=bar,hello=world,foo=bar1"` 86 | 87 | // Tests for simple enum tags 88 | Color string `json:"color" jsonschema:"enum=red,enum=green,enum=blue"` 89 | Rank int `json:"rank,omitempty" jsonschema:"enum=1,enum=2,enum=3"` 90 | Multiplier float64 `json:"mult,omitempty" jsonschema:"enum=1.0,enum=1.5,enum=2.0"` 91 | 92 | // Tests for enum tags on slices 93 | Roles []string `json:"roles" jsonschema:"enum=admin,enum=moderator,enum=user"` 94 | Priorities []int `json:"priorities,omitempty" jsonschema:"enum=-1,enum=0,enum=1,enun=2"` 95 | Offsets []float64 `json:"offsets,omitempty" jsonschema:"enum=1.570796,enum=3.141592,enum=6.283185"` 96 | 97 | // Test for raw JSON 98 | Raw json.RawMessage `json:"raw"` 99 | } 100 | 101 | type CustomTime time.Time 102 | 103 | type CustomTypeField struct { 104 | CreatedAt CustomTime 105 | } 106 | 107 | type CustomTimeWithInterface time.Time 108 | 109 | type CustomTypeFieldWithInterface struct { 110 | CreatedAt CustomTimeWithInterface 111 | } 112 | 113 | func (CustomTimeWithInterface) JSONSchemaType() *Type { 114 | return &Type{ 115 | Type: "string", 116 | Format: "date-time", 117 | } 118 | } 119 | 120 | type RootOneOf struct { 121 | Field1 string `json:"field1" jsonschema:"oneof_required=group1"` 122 | Field2 string `json:"field2" jsonschema:"oneof_required=group2"` 123 | Field3 interface{} `json:"field3" jsonschema:"oneof_type=string;array"` 124 | Field4 string `json:"field4" jsonschema:"oneof_required=group1"` 125 | Field5 ChildOneOf `json:"child"` 126 | } 127 | 128 | type ChildOneOf struct { 129 | Child1 string `json:"child1" jsonschema:"oneof_required=group1"` 130 | Child2 string `json:"child2" jsonschema:"oneof_required=group2"` 131 | Child3 interface{} `json:"child3" jsonschema:"oneof_required=group2,oneof_type=string;array"` 132 | Child4 string `json:"child4" jsonschema:"oneof_required=group1"` 133 | } 134 | 135 | type Outer struct { 136 | Inner 137 | } 138 | 139 | type Inner struct { 140 | Foo string `yaml:"foo"` 141 | } 142 | 143 | type MinValue struct { 144 | Value int `json:"value4" jsonschema_extras:"minimum=0"` 145 | } 146 | type Bytes []byte 147 | 148 | type TestNullable struct { 149 | Child1 string `json:"child1" jsonschema:"nullable"` 150 | } 151 | 152 | type TestYamlInline struct { 153 | Inlined Inner `yaml:",inline"` 154 | } 155 | 156 | type TestYamlAndJson struct { 157 | FirstName string `json:"FirstName" yaml:"first_name"` 158 | LastName string `json:"LastName"` 159 | Age uint `yaml:"age"` 160 | MiddleName string `yaml:"middle_name,omitempty" json:"MiddleName,omitempty"` 161 | } 162 | 163 | type CompactDate struct { 164 | Year int 165 | Month int 166 | } 167 | 168 | func (CompactDate) JSONSchemaType() *Type { 169 | return &Type{ 170 | Type: "string", 171 | Title: "Compact Date", 172 | Description: "Short date that only includes year and month", 173 | Pattern: "^[0-9]{4}-[0-1][0-9]$", 174 | } 175 | } 176 | 177 | type TestYamlAndJson2 struct { 178 | FirstName string `json:"FirstName" yaml:"first_name"` 179 | LastName string `json:"LastName"` 180 | Age uint `yaml:"age"` 181 | MiddleName string `yaml:"middle_name,omitempty" json:"MiddleName,omitempty"` 182 | } 183 | 184 | func (TestYamlAndJson2) GetFieldDocString(fieldName string) string { 185 | switch fieldName { 186 | case "FirstName": 187 | return "test2" 188 | case "LastName": 189 | return "test3" 190 | case "Age": 191 | return "test4" 192 | case "MiddleName": 193 | return "test5" 194 | default: 195 | return "" 196 | } 197 | } 198 | 199 | type CustomSliceOuter struct { 200 | Slice CustomSliceType `json:"slice"` 201 | } 202 | 203 | type CustomSliceType []string 204 | 205 | func (CustomSliceType) JSONSchemaType() *Type { 206 | return &Type{ 207 | OneOf: []*Type{{ 208 | Type: "string", 209 | }, { 210 | Type: "array", 211 | Items: &Type{ 212 | Type: "string", 213 | }, 214 | }}, 215 | } 216 | } 217 | 218 | type CustomMapType map[string]string 219 | 220 | func (CustomMapType) JSONSchemaType() *Type { 221 | properties := orderedmap.New() 222 | properties.Set("key", &Type{ 223 | Type: "string", 224 | }) 225 | properties.Set("value", &Type{ 226 | Type: "string", 227 | }) 228 | return &Type{ 229 | Type: "array", 230 | Items: &Type{ 231 | Type: "object", 232 | Properties: properties, 233 | Required: []string{"key", "value"}, 234 | }, 235 | } 236 | } 237 | 238 | type CustomMapOuter struct { 239 | MyMap CustomMapType `json:"my_map"` 240 | } 241 | 242 | func TestSchemaGeneration(t *testing.T) { 243 | tests := []struct { 244 | typ interface{} 245 | reflector *Reflector 246 | fixture string 247 | }{ 248 | {&RootOneOf{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/oneof.json"}, 249 | {&TestUser{}, &Reflector{}, "fixtures/defaults.json"}, 250 | {&TestUser{}, &Reflector{AllowAdditionalProperties: true}, "fixtures/allow_additional_props.json"}, 251 | {&TestUser{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/required_from_jsontags.json"}, 252 | {&TestUser{}, &Reflector{ExpandedStruct: true}, "fixtures/defaults_expanded_toplevel.json"}, 253 | {&TestUser{}, &Reflector{IgnoredTypes: []interface{}{GrandfatherType{}}}, "fixtures/ignore_type.json"}, 254 | {&TestUser{}, &Reflector{DoNotReference: true}, "fixtures/no_reference.json"}, 255 | {&TestUser{}, &Reflector{FullyQualifyTypeNames: true}, "fixtures/fully_qualified.json"}, 256 | {&TestUser{}, &Reflector{DoNotReference: true, FullyQualifyTypeNames: true}, "fixtures/no_ref_qual_types.json"}, 257 | {&CustomTypeField{}, &Reflector{ 258 | TypeMapper: func(i reflect.Type) *Type { 259 | if i == reflect.TypeOf(CustomTime{}) { 260 | return &Type{ 261 | Type: "string", 262 | Format: "date-time", 263 | } 264 | } 265 | return nil 266 | }, 267 | }, "fixtures/custom_type.json"}, 268 | {&TestUser{}, &Reflector{DoNotReference: true, FullyQualifyTypeNames: true}, "fixtures/no_ref_qual_types.json"}, 269 | {&Outer{}, &Reflector{ExpandedStruct: true, DoNotReference: true, YAMLEmbeddedStructs: true}, "fixtures/disable_inlining_embedded.json"}, 270 | {&MinValue{}, &Reflector{}, "fixtures/schema_with_minimum.json"}, 271 | {&TestNullable{}, &Reflector{}, "fixtures/nullable.json"}, 272 | {&TestYamlInline{}, &Reflector{YAMLEmbeddedStructs: true}, "fixtures/yaml_inline_embed.json"}, 273 | {&TestYamlInline{}, &Reflector{}, "fixtures/yaml_inline_embed.json"}, 274 | {&GrandfatherType{}, &Reflector{ 275 | AdditionalFields: func(r reflect.Type) []reflect.StructField { 276 | return []reflect.StructField{ 277 | { 278 | Name: "Addr", 279 | Type: reflect.TypeOf((*net.IP)(nil)).Elem(), 280 | Tag: "json:\"ip_addr\"", 281 | Anonymous: false, 282 | }, 283 | } 284 | }, 285 | }, "fixtures/custom_additional.json"}, 286 | {&TestYamlAndJson{}, &Reflector{PreferYAMLSchema: true}, "fixtures/test_yaml_and_json_prefer_yaml.json"}, 287 | {&TestYamlAndJson{}, &Reflector{}, "fixtures/test_yaml_and_json.json"}, 288 | // {&TestYamlAndJson2{}, &Reflector{}, "fixtures/test_yaml_and_json2.json"}, 289 | {&CompactDate{}, &Reflector{}, "fixtures/compact_date.json"}, 290 | {&CustomSliceOuter{}, &Reflector{}, "fixtures/custom_slice_type.json"}, 291 | {&CustomMapOuter{}, &Reflector{}, "fixtures/custom_map_type.json"}, 292 | {&CustomTypeFieldWithInterface{}, &Reflector{}, "fixtures/custom_type_with_interface.json"}, 293 | {&examples.User{}, prepareCommentReflector(t), "fixtures/go_comments.json"}, 294 | } 295 | 296 | for _, tt := range tests { 297 | name := strings.TrimSuffix(filepath.Base(tt.fixture), ".json") 298 | t.Run(name, func(t *testing.T) { 299 | f, err := ioutil.ReadFile(tt.fixture) 300 | require.NoError(t, err) 301 | 302 | actualSchema := tt.reflector.Reflect(tt.typ) 303 | expectedSchema := &Schema{} 304 | 305 | err = json.Unmarshal(f, expectedSchema) 306 | require.NoError(t, err) 307 | 308 | expectedJSON, _ := json.MarshalIndent(expectedSchema, "", " ") 309 | actualJSON, _ := json.MarshalIndent(actualSchema, "", " ") 310 | require.Equal(t, string(expectedJSON), string(actualJSON)) 311 | }) 312 | } 313 | } 314 | 315 | func prepareCommentReflector(t *testing.T) *Reflector { 316 | t.Helper() 317 | r := new(Reflector) 318 | err := r.AddGoComments("github.com/alecthomas/jsonschema", "./examples") 319 | require.NoError(t, err, "did not expect error while adding comments") 320 | return r 321 | } 322 | 323 | func TestBaselineUnmarshal(t *testing.T) { 324 | expectedJSON, err := ioutil.ReadFile("fixtures/defaults.json") 325 | require.NoError(t, err) 326 | 327 | reflector := &Reflector{} 328 | actualSchema := reflector.Reflect(&TestUser{}) 329 | 330 | actualJSON, _ := json.MarshalIndent(actualSchema, "", " ") 331 | 332 | require.Equal(t, strings.ReplaceAll(string(expectedJSON), `\/`, "/"), string(actualJSON)) 333 | } 334 | -------------------------------------------------------------------------------- /reflect.go: -------------------------------------------------------------------------------- 1 | // Package jsonschema uses reflection to generate JSON Schemas from Go types [1]. 2 | // 3 | // If json tags are present on struct fields, they will be used to infer 4 | // property names and if a property is required (omitempty is present). 5 | // 6 | // [1] http://json-schema.org/latest/json-schema-validation.html 7 | package jsonschema 8 | 9 | import ( 10 | "encoding/json" 11 | "net" 12 | "net/url" 13 | "reflect" 14 | "strconv" 15 | "strings" 16 | "time" 17 | 18 | "github.com/iancoleman/orderedmap" 19 | ) 20 | 21 | // Version is the JSON Schema version. 22 | // If extending JSON Schema with custom values use a custom URI. 23 | // RFC draft-wright-json-schema-00, section 6 24 | var Version = "http://json-schema.org/draft-04/schema#" 25 | 26 | // Schema is the root schema. 27 | // RFC draft-wright-json-schema-00, section 4.5 28 | type Schema struct { 29 | *Type 30 | Definitions Definitions 31 | } 32 | 33 | // customSchemaType is used to detect if the type provides it's own 34 | // custom Schema Type definition to use instead. Very useful for situations 35 | // where there are custom JSON Marshal and Unmarshal methods. 36 | type customSchemaType interface { 37 | JSONSchemaType() *Type 38 | } 39 | 40 | var customType = reflect.TypeOf((*customSchemaType)(nil)).Elem() 41 | 42 | // customSchemaGetFieldDocString 43 | type customSchemaGetFieldDocString interface { 44 | GetFieldDocString(fieldName string) string 45 | } 46 | 47 | type customGetFieldDocString func(fieldName string) string 48 | 49 | var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem() 50 | 51 | // Type represents a JSON Schema object type. 52 | type Type struct { 53 | // RFC draft-wright-json-schema-00 54 | Version string `json:"$schema,omitempty"` // section 6.1 55 | Ref string `json:"$ref,omitempty"` // section 7 56 | // RFC draft-wright-json-schema-validation-00, section 5 57 | MultipleOf int `json:"multipleOf,omitempty"` // section 5.1 58 | Maximum int `json:"maximum,omitempty"` // section 5.2 59 | ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` // section 5.3 60 | Minimum int `json:"minimum,omitempty"` // section 5.4 61 | ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` // section 5.5 62 | MaxLength int `json:"maxLength,omitempty"` // section 5.6 63 | MinLength int `json:"minLength,omitempty"` // section 5.7 64 | Pattern string `json:"pattern,omitempty"` // section 5.8 65 | AdditionalItems *Type `json:"additionalItems,omitempty"` // section 5.9 66 | Items *Type `json:"items,omitempty"` // section 5.9 67 | MaxItems int `json:"maxItems,omitempty"` // section 5.10 68 | MinItems int `json:"minItems,omitempty"` // section 5.11 69 | UniqueItems bool `json:"uniqueItems,omitempty"` // section 5.12 70 | MaxProperties int `json:"maxProperties,omitempty"` // section 5.13 71 | MinProperties int `json:"minProperties,omitempty"` // section 5.14 72 | Required []string `json:"required,omitempty"` // section 5.15 73 | Properties *orderedmap.OrderedMap `json:"properties,omitempty"` // section 5.16 74 | PatternProperties map[string]*Type `json:"patternProperties,omitempty"` // section 5.17 75 | AdditionalProperties json.RawMessage `json:"additionalProperties,omitempty"` // section 5.18 76 | Dependencies map[string]*Type `json:"dependencies,omitempty"` // section 5.19 77 | Enum []interface{} `json:"enum,omitempty"` // section 5.20 78 | Type string `json:"type,omitempty"` // section 5.21 79 | AllOf []*Type `json:"allOf,omitempty"` // section 5.22 80 | AnyOf []*Type `json:"anyOf,omitempty"` // section 5.23 81 | OneOf []*Type `json:"oneOf,omitempty"` // section 5.24 82 | Not *Type `json:"not,omitempty"` // section 5.25 83 | Definitions Definitions `json:"definitions,omitempty"` // section 5.26 84 | // RFC draft-wright-json-schema-validation-00, section 6, 7 85 | Title string `json:"title,omitempty"` // section 6.1 86 | Description string `json:"description,omitempty"` // section 6.1 87 | Default interface{} `json:"default,omitempty"` // section 6.2 88 | Format string `json:"format,omitempty"` // section 7 89 | Examples []interface{} `json:"examples,omitempty"` // section 7.4 90 | // RFC draft-handrews-json-schema-validation-02, section 9.4 91 | ReadOnly bool `json:"readOnly,omitempty"` 92 | WriteOnly bool `json:"writeOnly,omitempty"` 93 | // RFC draft-wright-json-schema-hyperschema-00, section 4 94 | Media *Type `json:"media,omitempty"` // section 4.3 95 | BinaryEncoding string `json:"binaryEncoding,omitempty"` // section 4.3 96 | 97 | Extras map[string]interface{} `json:"-"` 98 | } 99 | 100 | // Reflect reflects to Schema from a value using the default Reflector 101 | func Reflect(v interface{}) *Schema { 102 | return ReflectFromType(reflect.TypeOf(v)) 103 | } 104 | 105 | // ReflectFromType generates root schema using the default Reflector 106 | func ReflectFromType(t reflect.Type) *Schema { 107 | r := &Reflector{} 108 | return r.ReflectFromType(t) 109 | } 110 | 111 | // A Reflector reflects values into a Schema. 112 | type Reflector struct { 113 | // AllowAdditionalProperties will cause the Reflector to generate a schema 114 | // with additionalProperties to 'true' for all struct types. This means 115 | // the presence of additional keys in JSON objects will not cause validation 116 | // to fail. Note said additional keys will simply be dropped when the 117 | // validated JSON is unmarshaled. 118 | AllowAdditionalProperties bool 119 | 120 | // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema 121 | // that requires any key tagged with `jsonschema:required`, overriding the 122 | // default of requiring any key *not* tagged with `json:,omitempty`. 123 | RequiredFromJSONSchemaTags bool 124 | 125 | // YAMLEmbeddedStructs will cause the Reflector to generate a schema that does 126 | // not inline embedded structs. This should be enabled if the JSON schemas are 127 | // used with yaml.Marshal/Unmarshal. 128 | YAMLEmbeddedStructs bool 129 | 130 | // Prefer yaml: tags over json: tags to generate the schema even if json: tags 131 | // are present 132 | PreferYAMLSchema bool 133 | 134 | // ExpandedStruct will cause the toplevel definitions of the schema not 135 | // be referenced itself to a definition. 136 | ExpandedStruct bool 137 | 138 | // Do not reference definitions. 139 | // All types are still registered under the "definitions" top-level object, 140 | // but instead of $ref fields in containing types, the entire definition 141 | // of the contained type is inserted. 142 | // This will cause the entire structure of types to be output in one tree. 143 | DoNotReference bool 144 | 145 | // Use package paths as well as type names, to avoid conflicts. 146 | // Without this setting, if two packages contain a type with the same name, 147 | // and both are present in a schema, they will conflict and overwrite in 148 | // the definition map and produce bad output. This is particularly 149 | // noticeable when using DoNotReference. 150 | FullyQualifyTypeNames bool 151 | 152 | // IgnoredTypes defines a slice of types that should be ignored in the schema, 153 | // switching to just allowing additional properties instead. 154 | IgnoredTypes []interface{} 155 | 156 | // TypeMapper is a function that can be used to map custom Go types to jsonschema types. 157 | TypeMapper func(reflect.Type) *Type 158 | 159 | // TypeNamer allows customizing of type names 160 | TypeNamer func(reflect.Type) string 161 | 162 | // AdditionalFields allows adding structfields for a given type 163 | AdditionalFields func(reflect.Type) []reflect.StructField 164 | 165 | // CommentMap is a dictionary of fully qualified go types and fields to comment 166 | // strings that will be used if a description has not already been provided in 167 | // the tags. Types and fields are added to the package path using "." as a 168 | // separator. 169 | // 170 | // Type descriptions should be defined like: 171 | // 172 | // map[string]string{"github.com/alecthomas/jsonschema.Reflector": "A Reflector reflects values into a Schema."} 173 | // 174 | // And Fields defined as: 175 | // 176 | // map[string]string{"github.com/alecthomas/jsonschema.Reflector.DoNotReference": "Do not reference definitions."} 177 | // 178 | // See also: AddGoComments 179 | CommentMap map[string]string 180 | } 181 | 182 | // Reflect reflects to Schema from a value. 183 | func (r *Reflector) Reflect(v interface{}) *Schema { 184 | return r.ReflectFromType(reflect.TypeOf(v)) 185 | } 186 | 187 | // ReflectFromType generates root schema 188 | func (r *Reflector) ReflectFromType(t reflect.Type) *Schema { 189 | definitions := Definitions{} 190 | if r.ExpandedStruct { 191 | st := &Type{ 192 | Version: Version, 193 | Type: "object", 194 | Properties: orderedmap.New(), 195 | AdditionalProperties: []byte("false"), 196 | } 197 | if r.AllowAdditionalProperties { 198 | st.AdditionalProperties = []byte("true") 199 | } 200 | r.reflectStructFields(st, definitions, t) 201 | r.reflectStruct(definitions, t) 202 | delete(definitions, r.typeName(t)) 203 | return &Schema{Type: st, Definitions: definitions} 204 | } 205 | 206 | s := &Schema{ 207 | Type: r.reflectTypeToSchema(definitions, t), 208 | Definitions: definitions, 209 | } 210 | return s 211 | } 212 | 213 | // Definitions hold schema definitions. 214 | // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 215 | // RFC draft-wright-json-schema-validation-00, section 5.26 216 | type Definitions map[string]*Type 217 | 218 | // Available Go defined types for JSON Schema Validation. 219 | // RFC draft-wright-json-schema-validation-00, section 7.3 220 | var ( 221 | timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1 222 | ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5 223 | uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6 224 | ) 225 | 226 | // Byte slices will be encoded as base64 227 | var byteSliceType = reflect.TypeOf([]byte(nil)) 228 | 229 | // Except for json.RawMessage 230 | var rawMessageType = reflect.TypeOf(json.RawMessage{}) 231 | 232 | // Go code generated from protobuf enum types should fulfil this interface. 233 | type protoEnum interface { 234 | EnumDescriptor() ([]byte, []int) 235 | } 236 | 237 | var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem() 238 | 239 | func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Type { 240 | // Already added to definitions? 241 | if _, ok := definitions[r.typeName(t)]; ok && !r.DoNotReference { 242 | return &Type{Ref: "#/definitions/" + r.typeName(t)} 243 | } 244 | 245 | if r.TypeMapper != nil { 246 | if t := r.TypeMapper(t); t != nil { 247 | return t 248 | } 249 | } 250 | 251 | if rt := r.reflectCustomType(definitions, t); rt != nil { 252 | return rt 253 | } 254 | 255 | // jsonpb will marshal protobuf enum options as either strings or integers. 256 | // It will unmarshal either. 257 | if t.Implements(protoEnumType) { 258 | return &Type{OneOf: []*Type{ 259 | {Type: "string"}, 260 | {Type: "integer"}, 261 | }} 262 | } 263 | 264 | // Defined format types for JSON Schema Validation 265 | // RFC draft-wright-json-schema-validation-00, section 7.3 266 | // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7 267 | if t == ipType { 268 | // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5 269 | return &Type{Type: "string", Format: "ipv4"} // ipv4 RFC section 7.3.4 270 | } 271 | 272 | switch t.Kind() { 273 | case reflect.Struct: 274 | switch t { 275 | case timeType: // date-time RFC section 7.3.1 276 | return &Type{Type: "string", Format: "date-time"} 277 | case uriType: // uri RFC section 7.3.6 278 | return &Type{Type: "string", Format: "uri"} 279 | default: 280 | return r.reflectStruct(definitions, t) 281 | } 282 | 283 | case reflect.Map: 284 | switch t.Key().Kind() { 285 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 286 | rt := &Type{ 287 | Type: "object", 288 | PatternProperties: map[string]*Type{ 289 | "^[0-9]+$": r.reflectTypeToSchema(definitions, t.Elem()), 290 | }, 291 | AdditionalProperties: []byte("false"), 292 | } 293 | return rt 294 | } 295 | 296 | rt := &Type{ 297 | Type: "object", 298 | PatternProperties: map[string]*Type{ 299 | ".*": r.reflectTypeToSchema(definitions, t.Elem()), 300 | }, 301 | } 302 | delete(rt.PatternProperties, "additionalProperties") 303 | return rt 304 | 305 | case reflect.Slice, reflect.Array: 306 | returnType := &Type{} 307 | if t == rawMessageType { 308 | return &Type{ 309 | AdditionalProperties: []byte("true"), 310 | } 311 | } 312 | if t.Kind() == reflect.Array { 313 | returnType.MinItems = t.Len() 314 | returnType.MaxItems = returnType.MinItems 315 | } 316 | if t.Kind() == reflect.Slice && t.Elem() == byteSliceType.Elem() { 317 | returnType.Type = "string" 318 | returnType.Media = &Type{BinaryEncoding: "base64"} 319 | return returnType 320 | } 321 | returnType.Type = "array" 322 | returnType.Items = r.reflectTypeToSchema(definitions, t.Elem()) 323 | return returnType 324 | 325 | case reflect.Interface: 326 | return &Type{ 327 | AdditionalProperties: []byte("true"), 328 | } 329 | 330 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 331 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 332 | return &Type{Type: "integer"} 333 | 334 | case reflect.Float32, reflect.Float64: 335 | return &Type{Type: "number"} 336 | 337 | case reflect.Bool: 338 | return &Type{Type: "boolean"} 339 | 340 | case reflect.String: 341 | return &Type{Type: "string"} 342 | 343 | case reflect.Ptr: 344 | return r.reflectTypeToSchema(definitions, t.Elem()) 345 | } 346 | panic("unsupported type " + t.String()) 347 | } 348 | 349 | func (r *Reflector) reflectCustomType(definitions Definitions, t reflect.Type) *Type { 350 | if t.Kind() == reflect.Ptr { 351 | return r.reflectCustomType(definitions, t.Elem()) 352 | } 353 | 354 | if t.Implements(customType) { 355 | v := reflect.New(t) 356 | o := v.Interface().(customSchemaType) 357 | st := o.JSONSchemaType() 358 | definitions[r.typeName(t)] = st 359 | if r.DoNotReference { 360 | return st 361 | } else { 362 | return &Type{ 363 | Version: Version, 364 | Ref: "#/definitions/" + r.typeName(t), 365 | } 366 | } 367 | } 368 | 369 | return nil 370 | } 371 | 372 | // Reflects a struct to a JSON Schema type. 373 | func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type) *Type { 374 | if st := r.reflectCustomType(definitions, t); st != nil { 375 | return st 376 | } 377 | 378 | for _, ignored := range r.IgnoredTypes { 379 | if reflect.TypeOf(ignored) == t { 380 | st := &Type{ 381 | Type: "object", 382 | Properties: orderedmap.New(), 383 | AdditionalProperties: []byte("true"), 384 | } 385 | definitions[r.typeName(t)] = st 386 | 387 | if r.DoNotReference { 388 | return st 389 | } else { 390 | return &Type{ 391 | Version: Version, 392 | Ref: "#/definitions/" + r.typeName(t), 393 | } 394 | } 395 | } 396 | } 397 | 398 | st := &Type{ 399 | Type: "object", 400 | Properties: orderedmap.New(), 401 | AdditionalProperties: []byte("false"), 402 | Description: r.lookupComment(t, ""), 403 | } 404 | if r.AllowAdditionalProperties { 405 | st.AdditionalProperties = []byte("true") 406 | } 407 | definitions[r.typeName(t)] = st 408 | r.reflectStructFields(st, definitions, t) 409 | 410 | if r.DoNotReference { 411 | return st 412 | } else { 413 | return &Type{ 414 | Version: Version, 415 | Ref: "#/definitions/" + r.typeName(t), 416 | } 417 | } 418 | } 419 | 420 | func (r *Reflector) reflectStructFields(st *Type, definitions Definitions, t reflect.Type) { 421 | if t.Kind() == reflect.Ptr { 422 | t = t.Elem() 423 | } 424 | if t.Kind() != reflect.Struct { 425 | return 426 | } 427 | 428 | var getFieldDocString customGetFieldDocString 429 | if t.Implements(customStructGetFieldDocString) { 430 | v := reflect.New(t) 431 | o := v.Interface().(customSchemaGetFieldDocString) 432 | getFieldDocString = o.GetFieldDocString 433 | } 434 | 435 | handleField := func(f reflect.StructField) { 436 | name, shouldEmbed, required, nullable := r.reflectFieldName(f) 437 | // if anonymous and exported type should be processed recursively 438 | // current type should inherit properties of anonymous one 439 | if name == "" { 440 | if shouldEmbed { 441 | r.reflectStructFields(st, definitions, f.Type) 442 | } 443 | return 444 | } 445 | 446 | property := r.reflectTypeToSchema(definitions, f.Type) 447 | property.structKeywordsFromTags(f, st, name) 448 | if property.Description == "" { 449 | property.Description = r.lookupComment(t, f.Name) 450 | } 451 | if getFieldDocString != nil { 452 | property.Description = getFieldDocString(f.Name) 453 | } 454 | 455 | if nullable { 456 | property = &Type{ 457 | OneOf: []*Type{ 458 | property, 459 | { 460 | Type: "null", 461 | }, 462 | }, 463 | } 464 | } 465 | 466 | st.Properties.Set(name, property) 467 | if required { 468 | st.Required = append(st.Required, name) 469 | } 470 | } 471 | 472 | for i := 0; i < t.NumField(); i++ { 473 | f := t.Field(i) 474 | handleField(f) 475 | } 476 | if r.AdditionalFields != nil { 477 | if af := r.AdditionalFields(t); af != nil { 478 | for _, sf := range af { 479 | handleField(sf) 480 | } 481 | } 482 | } 483 | } 484 | 485 | func (r *Reflector) lookupComment(t reflect.Type, name string) string { 486 | if r.CommentMap == nil { 487 | return "" 488 | } 489 | 490 | n := fullyQualifiedTypeName(t) 491 | if name != "" { 492 | n = n + "." + name 493 | } 494 | 495 | return r.CommentMap[n] 496 | } 497 | 498 | func (t *Type) structKeywordsFromTags(f reflect.StructField, parentType *Type, propertyName string) { 499 | t.Description = f.Tag.Get("jsonschema_description") 500 | tags := strings.Split(f.Tag.Get("jsonschema"), ",") 501 | t.genericKeywords(tags, parentType, propertyName) 502 | switch t.Type { 503 | case "string": 504 | t.stringKeywords(tags) 505 | case "number": 506 | t.numbericKeywords(tags) 507 | case "integer": 508 | t.numbericKeywords(tags) 509 | case "array": 510 | t.arrayKeywords(tags) 511 | } 512 | extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",") 513 | t.extraKeywords(extras) 514 | } 515 | 516 | // read struct tags for generic keyworks 517 | func (t *Type) genericKeywords(tags []string, parentType *Type, propertyName string) { 518 | for _, tag := range tags { 519 | nameValue := strings.Split(tag, "=") 520 | if len(nameValue) == 2 { 521 | name, val := nameValue[0], nameValue[1] 522 | switch name { 523 | case "title": 524 | t.Title = val 525 | case "description": 526 | t.Description = val 527 | case "type": 528 | t.Type = val 529 | case "oneof_required": 530 | var typeFound *Type 531 | for i := range parentType.OneOf { 532 | if parentType.OneOf[i].Title == nameValue[1] { 533 | typeFound = parentType.OneOf[i] 534 | } 535 | } 536 | if typeFound == nil { 537 | typeFound = &Type{ 538 | Title: nameValue[1], 539 | Required: []string{}, 540 | } 541 | parentType.OneOf = append(parentType.OneOf, typeFound) 542 | } 543 | typeFound.Required = append(typeFound.Required, propertyName) 544 | case "oneof_type": 545 | if t.OneOf == nil { 546 | t.OneOf = make([]*Type, 0, 1) 547 | } 548 | t.Type = "" 549 | types := strings.Split(nameValue[1], ";") 550 | for _, ty := range types { 551 | t.OneOf = append(t.OneOf, &Type{ 552 | Type: ty, 553 | }) 554 | } 555 | case "enum": 556 | switch t.Type { 557 | case "string": 558 | t.Enum = append(t.Enum, val) 559 | case "integer": 560 | i, _ := strconv.Atoi(val) 561 | t.Enum = append(t.Enum, i) 562 | case "number": 563 | f, _ := strconv.ParseFloat(val, 64) 564 | t.Enum = append(t.Enum, f) 565 | } 566 | } 567 | } 568 | } 569 | } 570 | 571 | // read struct tags for string type keyworks 572 | func (t *Type) stringKeywords(tags []string) { 573 | for _, tag := range tags { 574 | nameValue := strings.Split(tag, "=") 575 | if len(nameValue) == 2 { 576 | name, val := nameValue[0], nameValue[1] 577 | switch name { 578 | case "minLength": 579 | i, _ := strconv.Atoi(val) 580 | t.MinLength = i 581 | case "maxLength": 582 | i, _ := strconv.Atoi(val) 583 | t.MaxLength = i 584 | case "pattern": 585 | t.Pattern = val 586 | case "format": 587 | switch val { 588 | case "date-time", "email", "hostname", "ipv4", "ipv6", "uri": 589 | t.Format = val 590 | break 591 | } 592 | case "readOnly": 593 | i, _ := strconv.ParseBool(val) 594 | t.ReadOnly = i 595 | case "writeOnly": 596 | i, _ := strconv.ParseBool(val) 597 | t.WriteOnly = i 598 | case "default": 599 | t.Default = val 600 | case "example": 601 | t.Examples = append(t.Examples, val) 602 | } 603 | } 604 | } 605 | } 606 | 607 | // read struct tags for numberic type keyworks 608 | func (t *Type) numbericKeywords(tags []string) { 609 | for _, tag := range tags { 610 | nameValue := strings.Split(tag, "=") 611 | if len(nameValue) == 2 { 612 | name, val := nameValue[0], nameValue[1] 613 | switch name { 614 | case "multipleOf": 615 | i, _ := strconv.Atoi(val) 616 | t.MultipleOf = i 617 | case "minimum": 618 | i, _ := strconv.Atoi(val) 619 | t.Minimum = i 620 | case "maximum": 621 | i, _ := strconv.Atoi(val) 622 | t.Maximum = i 623 | case "exclusiveMaximum": 624 | b, _ := strconv.ParseBool(val) 625 | t.ExclusiveMaximum = b 626 | case "exclusiveMinimum": 627 | b, _ := strconv.ParseBool(val) 628 | t.ExclusiveMinimum = b 629 | case "default": 630 | i, _ := strconv.Atoi(val) 631 | t.Default = i 632 | case "example": 633 | if i, err := strconv.Atoi(val); err == nil { 634 | t.Examples = append(t.Examples, i) 635 | } 636 | } 637 | } 638 | } 639 | } 640 | 641 | // read struct tags for object type keyworks 642 | // func (t *Type) objectKeywords(tags []string) { 643 | // for _, tag := range tags{ 644 | // nameValue := strings.Split(tag, "=") 645 | // name, val := nameValue[0], nameValue[1] 646 | // switch name{ 647 | // case "dependencies": 648 | // t.Dependencies = val 649 | // break; 650 | // case "patternProperties": 651 | // t.PatternProperties = val 652 | // break; 653 | // } 654 | // } 655 | // } 656 | 657 | // read struct tags for array type keyworks 658 | func (t *Type) arrayKeywords(tags []string) { 659 | var defaultValues []interface{} 660 | for _, tag := range tags { 661 | nameValue := strings.Split(tag, "=") 662 | if len(nameValue) == 2 { 663 | name, val := nameValue[0], nameValue[1] 664 | switch name { 665 | case "minItems": 666 | i, _ := strconv.Atoi(val) 667 | t.MinItems = i 668 | case "maxItems": 669 | i, _ := strconv.Atoi(val) 670 | t.MaxItems = i 671 | case "uniqueItems": 672 | t.UniqueItems = true 673 | case "default": 674 | defaultValues = append(defaultValues, val) 675 | case "enum": 676 | switch t.Items.Type { 677 | case "string": 678 | t.Items.Enum = append(t.Items.Enum, val) 679 | case "integer": 680 | i, _ := strconv.Atoi(val) 681 | t.Items.Enum = append(t.Items.Enum, i) 682 | case "number": 683 | f, _ := strconv.ParseFloat(val, 64) 684 | t.Items.Enum = append(t.Items.Enum, f) 685 | } 686 | } 687 | } 688 | } 689 | if len(defaultValues) > 0 { 690 | t.Default = defaultValues 691 | } 692 | } 693 | 694 | func (t *Type) extraKeywords(tags []string) { 695 | for _, tag := range tags { 696 | nameValue := strings.Split(tag, "=") 697 | if len(nameValue) == 2 { 698 | t.setExtra(nameValue[0], nameValue[1]) 699 | } 700 | } 701 | } 702 | 703 | func (t *Type) setExtra(key, val string) { 704 | if t.Extras == nil { 705 | t.Extras = map[string]interface{}{} 706 | } 707 | if existingVal, ok := t.Extras[key]; ok { 708 | switch existingVal := existingVal.(type) { 709 | case string: 710 | t.Extras[key] = []string{existingVal, val} 711 | case []string: 712 | t.Extras[key] = append(existingVal, val) 713 | case int: 714 | t.Extras[key], _ = strconv.Atoi(val) 715 | } 716 | } else { 717 | switch key { 718 | case "minimum": 719 | t.Extras[key], _ = strconv.Atoi(val) 720 | default: 721 | t.Extras[key] = val 722 | } 723 | } 724 | } 725 | 726 | func requiredFromJSONTags(tags []string) bool { 727 | if ignoredByJSONTags(tags) { 728 | return false 729 | } 730 | 731 | for _, tag := range tags[1:] { 732 | if tag == "omitempty" { 733 | return false 734 | } 735 | } 736 | return true 737 | } 738 | 739 | func requiredFromJSONSchemaTags(tags []string) bool { 740 | if ignoredByJSONSchemaTags(tags) { 741 | return false 742 | } 743 | for _, tag := range tags { 744 | if tag == "required" { 745 | return true 746 | } 747 | } 748 | return false 749 | } 750 | 751 | func nullableFromJSONSchemaTags(tags []string) bool { 752 | if ignoredByJSONSchemaTags(tags) { 753 | return false 754 | } 755 | for _, tag := range tags { 756 | if tag == "nullable" { 757 | return true 758 | } 759 | } 760 | return false 761 | } 762 | 763 | func inlineYAMLTags(tags []string) bool { 764 | for _, tag := range tags { 765 | if tag == "inline" { 766 | return true 767 | } 768 | } 769 | return false 770 | } 771 | 772 | func ignoredByJSONTags(tags []string) bool { 773 | return tags[0] == "-" 774 | } 775 | 776 | func ignoredByJSONSchemaTags(tags []string) bool { 777 | return tags[0] == "-" 778 | } 779 | 780 | func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool, bool) { 781 | jsonTags, exist := f.Tag.Lookup("json") 782 | yamlTags, yamlExist := f.Tag.Lookup("yaml") 783 | if !exist || r.PreferYAMLSchema { 784 | jsonTags = yamlTags 785 | exist = yamlExist 786 | } 787 | 788 | jsonTagsList := strings.Split(jsonTags, ",") 789 | yamlTagsList := strings.Split(yamlTags, ",") 790 | 791 | if ignoredByJSONTags(jsonTagsList) { 792 | return "", false, false, false 793 | } 794 | 795 | jsonSchemaTags := strings.Split(f.Tag.Get("jsonschema"), ",") 796 | if ignoredByJSONSchemaTags(jsonSchemaTags) { 797 | return "", false, false, false 798 | } 799 | 800 | name := f.Name 801 | required := requiredFromJSONTags(jsonTagsList) 802 | 803 | if r.RequiredFromJSONSchemaTags { 804 | required = requiredFromJSONSchemaTags(jsonSchemaTags) 805 | } 806 | 807 | nullable := nullableFromJSONSchemaTags(jsonSchemaTags) 808 | 809 | if jsonTagsList[0] != "" { 810 | name = jsonTagsList[0] 811 | } 812 | 813 | // field not anonymous and not export has no export name 814 | if !f.Anonymous && f.PkgPath != "" { 815 | name = "" 816 | } 817 | 818 | embed := false 819 | 820 | // field anonymous but without json tag should be inherited by current type 821 | if f.Anonymous && !exist { 822 | if !r.YAMLEmbeddedStructs { 823 | name = "" 824 | embed = true 825 | } else { 826 | name = strings.ToLower(name) 827 | } 828 | } 829 | 830 | if yamlExist && inlineYAMLTags(yamlTagsList) { 831 | name = "" 832 | embed = true 833 | } 834 | 835 | return name, embed, required, nullable 836 | } 837 | 838 | func (s *Schema) MarshalJSON() ([]byte, error) { 839 | b, err := json.Marshal(s.Type) 840 | if err != nil { 841 | return nil, err 842 | } 843 | if s.Definitions == nil || len(s.Definitions) == 0 { 844 | return b, nil 845 | } 846 | d, err := json.Marshal(struct { 847 | Definitions Definitions `json:"definitions,omitempty"` 848 | }{s.Definitions}) 849 | if err != nil { 850 | return nil, err 851 | } 852 | if len(b) == 2 { 853 | return d, nil 854 | } else { 855 | b[len(b)-1] = ',' 856 | return append(b, d[1:]...), nil 857 | } 858 | } 859 | 860 | func (t *Type) MarshalJSON() ([]byte, error) { 861 | type Type_ Type 862 | b, err := json.Marshal((*Type_)(t)) 863 | if err != nil { 864 | return nil, err 865 | } 866 | if t.Extras == nil || len(t.Extras) == 0 { 867 | return b, nil 868 | } 869 | m, err := json.Marshal(t.Extras) 870 | if err != nil { 871 | return nil, err 872 | } 873 | if len(b) == 2 { 874 | return m, nil 875 | } else { 876 | b[len(b)-1] = ',' 877 | return append(b, m[1:]...), nil 878 | } 879 | } 880 | 881 | func (r *Reflector) typeName(t reflect.Type) string { 882 | if r.TypeNamer != nil { 883 | if name := r.TypeNamer(t); name != "" { 884 | return name 885 | } 886 | } 887 | if r.FullyQualifyTypeNames { 888 | return fullyQualifiedTypeName(t) 889 | } 890 | return t.Name() 891 | } 892 | 893 | func fullyQualifiedTypeName(t reflect.Type) string { 894 | return t.PkgPath() + "." + t.Name() 895 | } 896 | 897 | // AddGoComments will update the reflectors comment map with all the comments 898 | // found in the provided source directories. See the #ExtractGoComments method 899 | // for more details. 900 | func (r *Reflector) AddGoComments(base, path string) error { 901 | if r.CommentMap == nil { 902 | r.CommentMap = make(map[string]string) 903 | } 904 | return ExtractGoComments(base, path, r.CommentMap) 905 | } 906 | --------------------------------------------------------------------------------