pax_global_header00006660000000000000000000000064147350332460014521gustar00rootroot0000000000000052 comment=328cc73c3399c73df18b708fa00d29669cb0e4cc jsonschema-0.13.0/000077500000000000000000000000001473503324600137345ustar00rootroot00000000000000jsonschema-0.13.0/.github/000077500000000000000000000000001473503324600152745ustar00rootroot00000000000000jsonschema-0.13.0/.github/workflows/000077500000000000000000000000001473503324600173315ustar00rootroot00000000000000jsonschema-0.13.0/.github/workflows/lint.yaml000066400000000000000000000006721473503324600211700ustar00rootroot00000000000000name: Lint on: push: tags: - v* branches: - main pull_request: jobs: lint: name: golangci-lint runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: "go.mod" - name: Lint uses: golangci/golangci-lint-action@v6 with: version: v1.62 jsonschema-0.13.0/.github/workflows/release.yaml000066400000000000000000000011521473503324600216340ustar00rootroot00000000000000# # Automatically tag a merge with master, or build a new image from the tag. # name: Release on: push: branches: - release #paths-ignore: # - "docs/**" jobs: tag-build-publish: name: Tag runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: "0" # make sure we get all commits! - name: Bump version and push tag id: bump uses: anothrNick/github-tag-action@1.52.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_BRANCHES: release WITH_V: true jsonschema-0.13.0/.github/workflows/test.yaml000066400000000000000000000013031473503324600211710ustar00rootroot00000000000000name: Test Go on: push: tags: - v* branches: - main pull_request: jobs: test: name: Test runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v4 with: go-version-file: "go.mod" - name: Install Dependencies env: GOPROXY: https://proxy.golang.org,direct run: go mod download - name: Test run: go test -race -coverprofile=coverage.out -covermode=atomic ./... - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} jsonschema-0.13.0/.gitignore000066400000000000000000000000171473503324600157220ustar00rootroot00000000000000vendor/ .idea/ jsonschema-0.13.0/.golangci.yml000066400000000000000000000032561473503324600163260ustar00rootroot00000000000000run: tests: true max-same-issues: 50 output: print-issued-lines: false linters: enable: - gocyclo - gocritic - goconst - dupl - unconvert - goimports - unused - govet - nakedret - errcheck - revive - ineffassign - goconst - unparam - gofmt linters-settings: vet: check-shadowing: true use-installed-packages: true dupl: threshold: 100 goconst: min-len: 8 min-occurrences: 3 gocyclo: min-complexity: 20 gocritic: disabled-checks: - ifElseChain gofmt: rewrite-rules: - pattern: "interface{}" replacement: "any" - pattern: "a[b:len(a)]" replacement: "a[b:]" issues: max-per-linter: 0 max-same: 0 exclude-dirs: - resources - old exclude-files: - cmd/protopkg/main.go exclude-use-default: false exclude: # Captured by errcheck. - "^(G104|G204):" # Very commonly not checked. - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*Print(f|ln|)|os\.(Un)?Setenv). is not checked' # Weird error only seen on Kochiku... - "internal error: no range for" - 'exported method `.*\.(MarshalJSON|UnmarshalJSON|URN|Payload|GoString|Close|Provides|Requires|ExcludeFromHash|MarshalText|UnmarshalText|Description|Check|Poll|Severity)` should have comment or be unexported' - "composite literal uses unkeyed fields" - 'declaration of "err" shadows declaration' - "by other packages, and that stutters" - "Potential file inclusion via variable" - "at least one file in a package should have a package comment" - "bad syntax for struct tag pair" jsonschema-0.13.0/COPYING000066400000000000000000000020371473503324600147710ustar00rootroot00000000000000Copyright (C) 2014 Alec Thomas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jsonschema-0.13.0/README.md000066400000000000000000000320331473503324600152140ustar00rootroot00000000000000# Go JSON Schema Reflection [![Lint](https://github.com/invopop/jsonschema/actions/workflows/lint.yaml/badge.svg)](https://github.com/invopop/jsonschema/actions/workflows/lint.yaml) [![Test Go](https://github.com/invopop/jsonschema/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/jsonschema/actions/workflows/test.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/invopop/jsonschema)](https://goreportcard.com/report/github.com/invopop/jsonschema) [![GoDoc](https://godoc.org/github.com/invopop/jsonschema?status.svg)](https://godoc.org/github.com/invopop/jsonschema) [![codecov](https://codecov.io/gh/invopop/jsonschema/graph/badge.svg?token=JMEB8W8GNZ)](https://codecov.io/gh/invopop/jsonschema) ![Latest Tag](https://img.shields.io/github/v/tag/invopop/jsonschema) This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection. - Supports arbitrarily complex types, including `interface{}`, maps, slices, etc. - Supports json-schema features such as minLength, maxLength, pattern, format, etc. - Supports simple string and numeric enums. - Supports custom property fields via the `jsonschema_extras` struct tag. This repository is a fork of the original [jsonschema](https://github.com/alecthomas/jsonschema) by [@alecthomas](https://github.com/alecthomas). At [Invopop](https://invopop.com) we use jsonschema as a cornerstone in our [GOBL library](https://github.com/invopop/gobl), and wanted to be able to continue building and adding features without taking up Alec's time. There have been a few significant changes that probably mean this version is a not compatible with with Alec's: - The original was stuck on the draft-04 version of JSON Schema, we've now moved to the latest JSON Schema Draft 2020-12. - Schema IDs are added automatically from the current Go package's URL in order to be unique, and can be disabled with the `Anonymous` option. - Support for the `FullyQualifyTypeName` option has been removed. If you have conflicts, you should use multiple schema files with different IDs, set the `DoNotReference` option to true to hide definitions completely, or add your own naming strategy using the `Namer` property. - Support for `yaml` tags and related options has been dropped for the sake of simplification. There were a [few inconsistencies](https://github.com/invopop/jsonschema/pull/21) around this that have now been fixed. ## Versions This project is still under v0 scheme, as per Go convention, breaking changes are likely. Please pin go modules to version tags or branches, and reach out if you think something can be improved. Go version >= 1.18 is required as generics are now being used. ## Example The following Go type: ```go type TestUser struct { ID int `json:"id"` Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` Tags map[string]interface{} `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` Metadata interface{} `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` } ``` Results in following JSON Schema: ```go jsonschema.Reflect(&TestUser{}) ``` ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema_test/test-user", "$ref": "#/$defs/TestUser", "$defs": { "TestUser": { "oneOf": [ { "required": ["birth_date"], "title": "date" }, { "required": ["year_of_birth"], "title": "year" } ], "properties": { "id": { "type": "integer" }, "name": { "type": "string", "title": "the name", "description": "The name of a friend", "default": "alex", "examples": ["joe", "lucy"] }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "The list of IDs, omitted when empty" }, "tags": { "type": "object", "a": "b", "foo": ["bar", "bar1"] }, "birth_date": { "type": "string", "format": "date-time" }, "year_of_birth": { "type": "string" }, "metadata": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "fav_color": { "type": "string", "enum": ["red", "green", "blue"] } }, "additionalProperties": false, "type": "object", "required": ["id", "name"] } } } ``` ## YAML Support for `yaml` tags has now been removed. If you feel very strongly about this, we've opened a discussion to hear your comments: https://github.com/invopop/jsonschema/discussions/28 The recommended approach if you need to deal with YAML data is to first convert to JSON. The [invopop/yaml](https://github.com/invopop/yaml) library will make this trivial. ## Configurable behaviour The behaviour of the schema generator can be altered with parameters when a `jsonschema.Reflector` instance is created. ### ExpandedStruct If set to `true`, makes the top level struct not to reference itself in the definitions. But type passed should be a struct type. eg. ```go type GrandfatherType struct { FamilyName string `json:"family_name" jsonschema:"required"` } type SomeBaseType struct { SomeBaseProperty int `json:"some_base_property"` // The jsonschema required tag is nonsensical for private and ignored properties. // Their presence here tests that the fields *will not* be required in the output // schema, even if they are tagged required. somePrivateBaseProperty string `json:"i_am_private" jsonschema:"required"` SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` SomeSchemaIgnoredProperty string `jsonschema:"-,required"` SomeUntaggedBaseProperty bool `jsonschema:"required"` someUnexportedUntaggedBaseProperty bool Grandfather GrandfatherType `json:"grand"` } ``` will output: ```json { "$schema": "http://json-schema.org/draft/2020-12/schema", "required": ["some_base_property", "grand", "SomeUntaggedBaseProperty"], "properties": { "SomeUntaggedBaseProperty": { "type": "boolean" }, "grand": { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/definitions/GrandfatherType" }, "some_base_property": { "type": "integer" } }, "type": "object", "$defs": { "GrandfatherType": { "required": ["family_name"], "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object" } } } ``` ### Using Go Comments Writing a good schema with descriptions inside tags can become cumbersome and tedious, especially if you already have some Go comments around your types and field definitions. If you'd like to take advantage of these existing comments, you can use the `AddGoComments(base, path string)` method that forms part of the reflector to parse your go files and automatically generate a dictionary of Go import paths, types, and fields, to individual comments. These will then be used automatically as description fields, and can be overridden with a manual definition if needed. Take a simplified example of a User struct which for the sake of simplicity we assume is defined inside this package: ```go package main // User is used as a base to provide tests for comments. type User struct { // Unique sequential identifier. ID int `json:"id" jsonschema:"required"` // Name of the user Name string `json:"name"` } ``` To get the comments provided into your JSON schema, use a regular `Reflector` and add the go code using an import module URL and path. Fully qualified go module paths cannot be determined reliably by the `go/parser` library, so we need to introduce this manually: ```go r := new(Reflector) if err := r.AddGoComments("github.com/invopop/jsonschema", "./"); err != nil { // deal with error } s := r.Reflect(&User{}) // output ``` Expect the results to be similar to: ```json { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/User", "$defs": { "User": { "required": ["id"], "properties": { "id": { "type": "integer", "description": "Unique sequential identifier." }, "name": { "type": "string", "description": "Name of the user" } }, "additionalProperties": false, "type": "object", "description": "User is used as a base to provide tests for comments." } } } ``` ### Custom Key Naming In some situations, the keys actually used to write files are different from Go structs'. This is often the case when writing a configuration file to YAML or JSON from a Go struct, or when returning a JSON response for a Web API: APIs typically use snake_case, while Go uses PascalCase. You can pass a `func(string) string` function to `Reflector`'s `KeyNamer` option to map Go field names to JSON key names and reflect the aforementioned transformations, without having to specify `json:"..."` on every struct field. For example, consider the following struct ```go type User struct { GivenName string PasswordSalted []byte `json:"salted_password"` } ``` We can transform field names to snake_case in the generated JSON schema: ```go r := new(jsonschema.Reflector) r.KeyNamer = strcase.SnakeCase // from package github.com/stoewer/go-strcase r.Reflect(&User{}) ``` Will yield ```diff { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/User", "$defs": { "User": { "properties": { - "GivenName": { + "given_name": { "type": "string" }, "salted_password": { "type": "string", "contentEncoding": "base64" } }, "additionalProperties": false, "type": "object", - "required": ["GivenName", "salted_password"] + "required": ["given_name", "salted_password"] } } } ``` As you can see, if a field name has a `json:""` tag set, the `key` argument to `KeyNamer` will have the value of that tag. ### Custom Type Definitions Sometimes it can be useful to have custom JSON Marshal and Unmarshal methods in your structs that automatically convert for example a string into an object. This library will recognize and attempt to call four different methods that help you adjust schemas to your specific needs: - `JSONSchema() *Schema` - will prevent auto-generation of the schema so that you can provide your own definition. - `JSONSchemaExtend(schema *jsonschema.Schema)` - will be called _after_ the schema has been generated, allowing you to add or manipulate the fields easily. - `JSONSchemaAlias() any` - is called when reflecting the type of object and allows for an alternative to be used instead. - `JSONSchemaProperty(prop string) any` - will be called for every property inside a struct giving you the chance to provide an alternative object to convert into a schema. Note that all of these methods **must** be defined on a non-pointer object for them to be called. Take the following simplified example of a `CompactDate` that only includes the Year and Month: ```go type CompactDate struct { Year int Month int } func (d *CompactDate) UnmarshalJSON(data []byte) error { if len(data) != 9 { return errors.New("invalid compact date length") } var err error d.Year, err = strconv.Atoi(string(data[1:5])) if err != nil { return err } d.Month, err = strconv.Atoi(string(data[7:8])) if err != nil { return err } return nil } func (d *CompactDate) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) buf.WriteByte('"') buf.WriteString(fmt.Sprintf("%d-%02d", d.Year, d.Month)) buf.WriteByte('"') return buf.Bytes(), nil } func (CompactDate) JSONSchema() *Schema { return &Schema{ Type: "string", Title: "Compact Date", Description: "Short date that only includes year and month", Pattern: "^[0-9]{4}-[0-1][0-9]$", } } ``` The resulting schema generated for this struct would look like: ```json { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/CompactDate", "$defs": { "CompactDate": { "pattern": "^[0-9]{4}-[0-1][0-9]$", "type": "string", "title": "Compact Date", "description": "Short date that only includes year and month" } } } ``` jsonschema-0.13.0/examples/000077500000000000000000000000001473503324600155525ustar00rootroot00000000000000jsonschema-0.13.0/examples/nested/000077500000000000000000000000001473503324600170345ustar00rootroot00000000000000jsonschema-0.13.0/examples/nested/nested.go000066400000000000000000000012631473503324600206470ustar00rootroot00000000000000package nested // Pet defines the user's fury friend. type Pet struct { // Name of the animal. Name string `json:"name" jsonschema:"title=Name"` } // Pets is a collection of Pet objects. type Pets []*Pet // NamedPets is a map of animal names to pets. type NamedPets map[string]*Pet type ( // Plant represents the plants the user might have and serves as a test // of structs inside a `type` set. Plant struct { Variant string `json:"variant" jsonschema:"title=Variant"` // This comment will be used // Multicellular is true if the plant is multicellular Multicellular bool `json:"multicellular,omitempty" jsonschema:"title=Multicellular"` // This comment will be ignored } ) jsonschema-0.13.0/examples/user.go000066400000000000000000000016131473503324600170600ustar00rootroot00000000000000package examples import ( "github.com/invopop/jsonschema/examples/nested" ) // User is used as a base to provide tests for comments. // Don't forget to checkout the nested path. type User struct { // Unique sequential identifier. ID int `json:"id" jsonschema:"required"` // This comment will be ignored 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"` Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` Tags map[string]any `json:"tags,omitempty"` // An array of pets the user cares for. Pets nested.Pets `json:"pets"` // Set of animal names to pets NamedPets nested.NamedPets `json:"named_pets"` // Set of plants that the user likes Plants []*nested.Plant `json:"plants" jsonschema:"title=Plants"` } jsonschema-0.13.0/examples_test.go000066400000000000000000000061201473503324600171370ustar00rootroot00000000000000package jsonschema_test import ( "encoding/json" "fmt" "time" "github.com/invopop/jsonschema" ) type SampleUser struct { ID int `json:"id"` Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` Tags map[string]any `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` Metadata any `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` } func ExampleReflect() { s := jsonschema.Reflect(&SampleUser{}) data, err := json.MarshalIndent(s, "", " ") if err != nil { panic(err.Error()) } fmt.Println(string(data)) // Output: // { // "$schema": "https://json-schema.org/draft/2020-12/schema", // "$id": "https://github.com/invopop/jsonschema_test/sample-user", // "$ref": "#/$defs/SampleUser", // "$defs": { // "SampleUser": { // "oneOf": [ // { // "required": [ // "birth_date" // ], // "title": "date" // }, // { // "required": [ // "year_of_birth" // ], // "title": "year" // } // ], // "properties": { // "id": { // "type": "integer" // }, // "name": { // "type": "string", // "title": "the name", // "description": "The name of a friend", // "default": "alex", // "examples": [ // "joe", // "lucy" // ] // }, // "friends": { // "items": { // "type": "integer" // }, // "type": "array", // "description": "The list of IDs, omitted when empty" // }, // "tags": { // "type": "object", // "a": "b", // "foo": [ // "bar", // "bar1" // ] // }, // "birth_date": { // "type": "string", // "format": "date-time" // }, // "year_of_birth": { // "type": "string" // }, // "metadata": { // "oneOf": [ // { // "type": "string" // }, // { // "type": "array" // } // ] // }, // "fav_color": { // "type": "string", // "enum": [ // "red", // "green", // "blue" // ] // } // }, // "additionalProperties": false, // "type": "object", // "required": [ // "id", // "name" // ] // } // } // } } jsonschema-0.13.0/fixtures/000077500000000000000000000000001473503324600156055ustar00rootroot00000000000000jsonschema-0.13.0/fixtures/.gitignore000066400000000000000000000000121473503324600175660ustar00rootroot00000000000000*.out.jsonjsonschema-0.13.0/fixtures/allow_additional_props.json000066400000000000000000000112751473503324600232370ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "photo", "photo2", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.13.0/fixtures/anyof.json000066400000000000000000000033031473503324600176130ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/root-any-of", "$ref": "#/$defs/RootAnyOf", "$defs": { "ChildAnyOf": { "anyOf": [ { "required": [ "child1", "child4" ], "title": "group1" }, { "required": [ "child2", "child3" ], "title": "group2" } ], "properties": { "child1": { "type": "string" }, "child2": { "type": "string" }, "child3": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "child4": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "RootAnyOf": { "anyOf": [ { "required": [ "field1", "field4" ], "title": "group1" }, { "required": [ "field2" ], "title": "group2" } ], "properties": { "field1": { "type": "string" }, "field2": { "type": "string" }, "field3": { "anyOf": [ { "type": "string" }, { "type": "array" } ] }, "field4": { "type": "string" }, "child": { "$ref": "#/$defs/ChildAnyOf" } }, "additionalProperties": false, "type": "object" } } }jsonschema-0.13.0/fixtures/array_handling.json000066400000000000000000000013161473503324600214630ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/array-handler", "$ref": "#/$defs/ArrayHandler", "$defs": { "ArrayHandler": { "properties": { "min_len": { "items": { "type": "string", "minLength": 2 }, "type": "array", "default": [ "qwerty" ] }, "min_val": { "items": { "type": "number", "minimum": 2.5 }, "type": "array" } }, "additionalProperties": false, "type": "object", "required": [ "min_len", "min_val" ] } } }jsonschema-0.13.0/fixtures/array_type.json000066400000000000000000000004171473503324600206610ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/array-type", "$ref": "#/$defs/ArrayType", "$defs": { "ArrayType": { "items": { "type": "string" }, "type": "array" } } }jsonschema-0.13.0/fixtures/base_schema_id.json000066400000000000000000000013531473503324600214100ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/schemas/lookup-user", "$ref": "#/$defs/LookupUser", "$defs": { "LookupName": { "properties": { "first": { "type": "string" }, "surname": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "first", "surname" ] }, "LookupUser": { "properties": { "name": { "$ref": "#/$defs/LookupName" }, "alias": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] } } }jsonschema-0.13.0/fixtures/commas_in_pattern.json000066400000000000000000000007621473503324600222070ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/pattern-test", "$ref": "#/$defs/PatternTest", "$defs": { "PatternTest": { "properties": { "with_pattern": { "type": "string", "maxLength": 50, "minLength": 1, "pattern": "[0-9]{1,4}" } }, "additionalProperties": false, "type": "object", "required": [ "with_pattern" ] } } }jsonschema-0.13.0/fixtures/compact_date.json000066400000000000000000000005611473503324600211250ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/compact-date", "$ref": "#/$defs/CompactDate", "$defs": { "CompactDate": { "type": "string", "pattern": "^[0-9]{4}-[0-1][0-9]$", "title": "Compact Date", "description": "Short date that only includes year and month" } } }jsonschema-0.13.0/fixtures/custom_additional.json000066400000000000000000000010161473503324600222000ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/grandfather-type", "$ref": "#/$defs/GrandfatherType", "$defs": { "GrandfatherType": { "properties": { "family_name": { "type": "string" }, "ip_addr": { "type": "string", "format": "ipv4" } }, "additionalProperties": false, "type": "object", "required": [ "family_name", "ip_addr" ] } } }jsonschema-0.13.0/fixtures/custom_base_schema_id.json000066400000000000000000000104321473503324600230000ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft/2020-12/schema", "$id": "http://example.com/schema/TestUser", "$ref": "#/$defs/TestUser", "$defs": { "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "TestUser": { "properties": { "some_base_property": { "type": "integer" }, "some_base_property_yaml": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "id": { "type": "integer" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "type": "string", "contentEncoding": "base64" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "some_base_property", "some_base_property_yaml", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "id", "name", "password", "TestFlag", "age", "email", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.13.0/fixtures/custom_comments.json000066400000000000000000000061501473503324600217210ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/examples/user", "$ref": "#/$defs/User", "$defs": { "NamedPets": { "additionalProperties": { "$ref": "#/$defs/Pet" }, "type": "object", "description": "NamedPets is a map of animal names to pets." }, "Pet": { "properties": { "name": { "type": "string", "title": "Name", "description": "Name of the animal." } }, "additionalProperties": false, "type": "object", "required": [ "name" ], "description": "Pet defines the user's fury friend." }, "Pets": { "items": { "$ref": "#/$defs/Pet" }, "type": "array", "description": "Pets is a collection of Pet objects." }, "Plant": { "properties": { "variant": { "type": "string", "title": "Variant", "description": "This comment will be used" }, "multicellular": { "type": "boolean", "title": "Multicellular", "description": "Multicellular is true if the plant is multicellular" } }, "additionalProperties": false, "type": "object", "required": [ "variant" ], "description": "Plant represents the plants the user might have and serves as a test of structs inside a `type` set." }, "User": { "properties": { "id": { "type": "integer", "description": "Field ID of Go type github.com/invopop/jsonschema/examples.User." }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "examples": [ "joe", "lucy" ] }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "type": "object", "description": "Field Tags of Go type github.com/invopop/jsonschema/examples.User." }, "pets": { "$ref": "#/$defs/Pets", "description": "Field Pets of Go type github.com/invopop/jsonschema/examples.User." }, "named_pets": { "$ref": "#/$defs/NamedPets", "description": "Field NamedPets of Go type github.com/invopop/jsonschema/examples.User." }, "plants": { "items": { "$ref": "#/$defs/Plant" }, "type": "array", "title": "Plants", "description": "Field Plants of Go type github.com/invopop/jsonschema/examples.User." } }, "additionalProperties": false, "type": "object", "required": [ "id", "name", "pets", "named_pets", "plants" ], "description": "Go type User, defined in package github.com/invopop/jsonschema/examples." } } }jsonschema-0.13.0/fixtures/custom_map_type.json000066400000000000000000000013601473503324600217100ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/custom-map-outer", "$ref": "#/$defs/CustomMapOuter", "$defs": { "CustomMapOuter": { "properties": { "my_map": { "$ref": "#/$defs/CustomMapType" } }, "additionalProperties": false, "type": "object", "required": [ "my_map" ] }, "CustomMapType": { "items": { "properties": { "key": { "type": "string" }, "value": { "type": "string" } }, "type": "object", "required": [ "key", "value" ] }, "type": "array" } } }jsonschema-0.13.0/fixtures/custom_slice_type.json000066400000000000000000000012031473503324600222260ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/custom-slice-outer", "$ref": "#/$defs/CustomSliceOuter", "$defs": { "CustomSliceOuter": { "properties": { "slice": { "$ref": "#/$defs/CustomSliceType" } }, "additionalProperties": false, "type": "object", "required": [ "slice" ] }, "CustomSliceType": { "oneOf": [ { "type": "string" }, { "items": { "type": "string" }, "type": "array" } ] } } }jsonschema-0.13.0/fixtures/custom_type.json000066400000000000000000000007021473503324600210520ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/custom-type-field", "$ref": "#/$defs/CustomTypeField", "$defs": { "CustomTypeField": { "properties": { "CreatedAt": { "type": "string", "format": "date-time" } }, "additionalProperties": false, "type": "object", "required": [ "CreatedAt" ] } } }jsonschema-0.13.0/fixtures/custom_type_extend.json000066400000000000000000000010161473503324600224200ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/schema-extend-test", "$ref": "#/$defs/SchemaExtendTest", "$defs": { "SchemaExtendTest": { "properties": { "LastName": { "type": "string", "description": "some extra words" }, "middle_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "LastName" ] } } }jsonschema-0.13.0/fixtures/custom_type_with_interface.json000066400000000000000000000010771473503324600241330ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/custom-type-field-with-interface", "$ref": "#/$defs/CustomTypeFieldWithInterface", "$defs": { "CustomTimeWithInterface": { "type": "string", "format": "date-time" }, "CustomTypeFieldWithInterface": { "properties": { "CreatedAt": { "$ref": "#/$defs/CustomTimeWithInterface" } }, "additionalProperties": false, "type": "object", "required": [ "CreatedAt" ] } } }jsonschema-0.13.0/fixtures/defaults_expanded_toplevel.json000066400000000000000000000076451473503324600241050ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" } }, "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "photo", "photo2", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] }jsonschema-0.13.0/fixtures/equals_in_pattern.json000066400000000000000000000011201473503324600222070ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/pattern-equals-test", "$ref": "#/$defs/PatternEqualsTest", "$defs": { "PatternEqualsTest": { "properties": { "WithEquals": { "type": "string", "pattern": "foo=bar" }, "WithEqualsAndCommas": { "type": "string", "pattern": "foo,=bar" } }, "additionalProperties": false, "type": "object", "required": [ "WithEquals", "WithEqualsAndCommas" ] } } }jsonschema-0.13.0/fixtures/go_comments.json000066400000000000000000000055461473503324600210240ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/examples/user", "$ref": "#/$defs/User", "$defs": { "NamedPets": { "additionalProperties": { "$ref": "#/$defs/Pet" }, "type": "object", "description": "NamedPets is a map of animal names to pets." }, "Pet": { "properties": { "name": { "type": "string", "title": "Name", "description": "Name of the animal." } }, "additionalProperties": false, "type": "object", "required": [ "name" ], "description": "Pet defines the user's fury friend." }, "Pets": { "items": { "$ref": "#/$defs/Pet" }, "type": "array", "description": "Pets is a collection of Pet objects." }, "Plant": { "properties": { "variant": { "type": "string", "title": "Variant", "description": "This comment will be used" }, "multicellular": { "type": "boolean", "title": "Multicellular", "description": "Multicellular is true if the plant is multicellular" } }, "additionalProperties": false, "type": "object", "required": [ "variant" ], "description": "Plant represents the plants the user might have and serves as a test of structs inside a `type` set." }, "User": { "properties": { "id": { "type": "integer", "description": "Unique sequential identifier." }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "examples": [ "joe", "lucy" ] }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "type": "object" }, "pets": { "$ref": "#/$defs/Pets", "description": "An array of pets the user cares for." }, "named_pets": { "$ref": "#/$defs/NamedPets", "description": "Set of animal names to pets" }, "plants": { "items": { "$ref": "#/$defs/Plant" }, "type": "array", "title": "Plants", "description": "Set of plants that the user likes" } }, "additionalProperties": false, "type": "object", "required": [ "id", "name", "pets", "named_pets", "plants" ], "description": "User is used as a base to provide tests for comments." } } }jsonschema-0.13.0/fixtures/go_comments_full.json000066400000000000000000000056221473503324600220410ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/examples/user", "$ref": "#/$defs/User", "$defs": { "NamedPets": { "additionalProperties": { "$ref": "#/$defs/Pet" }, "type": "object", "description": "NamedPets is a map of animal names to pets." }, "Pet": { "properties": { "name": { "type": "string", "title": "Name", "description": "Name of the animal." } }, "additionalProperties": false, "type": "object", "required": [ "name" ], "description": "Pet defines the user's fury friend." }, "Pets": { "items": { "$ref": "#/$defs/Pet" }, "type": "array", "description": "Pets is a collection of Pet objects." }, "Plant": { "properties": { "variant": { "type": "string", "title": "Variant", "description": "This comment will be used" }, "multicellular": { "type": "boolean", "title": "Multicellular", "description": "Multicellular is true if the plant is multicellular" } }, "additionalProperties": false, "type": "object", "required": [ "variant" ], "description": "Plant represents the plants the user might have and serves as a test\nof structs inside a `type` set." }, "User": { "properties": { "id": { "type": "integer", "description": "Unique sequential identifier." }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "examples": [ "joe", "lucy" ] }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "type": "object" }, "pets": { "$ref": "#/$defs/Pets", "description": "An array of pets the user cares for." }, "named_pets": { "$ref": "#/$defs/NamedPets", "description": "Set of animal names to pets" }, "plants": { "items": { "$ref": "#/$defs/Plant" }, "type": "array", "title": "Plants", "description": "Set of plants that the user likes" } }, "additionalProperties": false, "type": "object", "required": [ "id", "name", "pets", "named_pets", "plants" ], "description": "User is used as a base to provide tests for comments.\nDon't forget to checkout the nested path." } } }jsonschema-0.13.0/fixtures/ignore_type.json000066400000000000000000000112171473503324600210260ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": {}, "additionalProperties": false, "type": "object" }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "photo", "photo2", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.13.0/fixtures/inlining_embedded.json000066400000000000000000000010541473503324600221200ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/outer-named", "$defs": { "Inner": { "properties": { "Foo": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "Foo" ] } }, "properties": { "text": { "type": "string" }, "inner": { "$ref": "#/$defs/Inner" } }, "additionalProperties": false, "type": "object", "required": [ "inner" ] }jsonschema-0.13.0/fixtures/inlining_embedded_anchored.json000066400000000000000000000011411473503324600237600ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/outer-named", "$anchor": "OuterNamed", "$defs": { "Inner": { "$anchor": "Inner", "properties": { "Foo": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "Foo" ] } }, "properties": { "text": { "type": "string" }, "inner": { "$ref": "#/$defs/Inner" } }, "additionalProperties": false, "type": "object", "required": [ "inner" ] }jsonschema-0.13.0/fixtures/inlining_inheritance.json000066400000000000000000000005721473503324600226640ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/outer", "properties": { "TextNamed": { "type": "string" }, "Text": { "type": "string" }, "Foo": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "TextNamed", "Foo" ] }jsonschema-0.13.0/fixtures/inlining_ptr.json000066400000000000000000000004741473503324600212010ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/outer-ptr", "properties": { "Foo": { "type": "string" }, "Text": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "Foo" ] }jsonschema-0.13.0/fixtures/inlining_tag.json000066400000000000000000000005001473503324600211350ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/outer-inlined", "properties": { "text": { "type": "string" }, "Foo": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "Foo" ] }jsonschema-0.13.0/fixtures/keynamed.json000066400000000000000000000025041473503324600202760ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/key-named", "$ref": "#/$defs/KeyNamed", "$defs": { "KeyNamed": { "properties": { "ThisWasLeftAsIs": { "type": "string" }, "coming_from_json_tag": { "type": "boolean" }, "nested_not_renamed": { "$ref": "#/$defs/KeyNamedNested" }, "✨unicode✨ s̸̥͝h̷̳͒e̴̜̽n̸̡̿a̷̘̔n̷̘͐i̶̫̐ǵ̶̯a̵̘͒n̷̮̾s̸̟̓": { "type": "string" }, "20.01": { "type": "integer", "description": "Description was preserved" } }, "additionalProperties": false, "type": "object", "required": [ "ThisWasLeftAsIs", "coming_from_json_tag", "nested_not_renamed", "✨unicode✨ s̸̥͝h̷̳͒e̴̜̽n̸̡̿a̷̘̔n̷̘͐i̶̫̐ǵ̶̯a̵̘͒n̷̮̾s̸̟̓", "20.01" ] }, "KeyNamedNested": { "properties": { "nested-renamed-property": { "type": "string" }, "NotRenamed": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "nested-renamed-property", "NotRenamed" ] } } }jsonschema-0.13.0/fixtures/lookup.json000066400000000000000000000007271473503324600200170ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/schemas/lookup-user", "$ref": "#/$defs/LookupUser", "$defs": { "LookupUser": { "properties": { "name": { "$ref": "https://example.com/schemas/lookup-name" }, "alias": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] } } }jsonschema-0.13.0/fixtures/lookup_expanded.json000066400000000000000000000005631473503324600216650ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/schemas/lookup-user", "$anchor": "LookupUser", "properties": { "name": { "$ref": "https://example.com/schemas/lookup-name" }, "alias": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] }jsonschema-0.13.0/fixtures/map_type.json000066400000000000000000000003271473503324600203200ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/map-type", "$ref": "#/$defs/MapType", "$defs": { "MapType": { "type": "object" } } }jsonschema-0.13.0/fixtures/no_reference.json000066400000000000000000000073401473503324600211360ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "type": "object" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "type": "string", "contentEncoding": "base64" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "photo", "photo2", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] }jsonschema-0.13.0/fixtures/no_reference_anchor.json000066400000000000000000000074351473503324600224750ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "$anchor": "TestUser", "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$anchor": "GrandfatherType", "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "type": "object" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "type": "string", "contentEncoding": "base64" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "photo", "photo2", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] }jsonschema-0.13.0/fixtures/nullable.json000066400000000000000000000010141473503324600202720ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-nullable", "$ref": "#/$defs/TestNullable", "$defs": { "TestNullable": { "properties": { "child1": { "oneOf": [ { "type": "string" }, { "type": "null" } ] } }, "additionalProperties": false, "type": "object", "required": [ "child1" ] } } }jsonschema-0.13.0/fixtures/number_handling.json000066400000000000000000000010251473503324600216320ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/number-handler", "$ref": "#/$defs/NumberHandler", "$defs": { "NumberHandler": { "properties": { "int64": { "type": "integer", "default": 12 }, "float32": { "type": "number", "default": 12.5 } }, "additionalProperties": false, "type": "object", "required": [ "int64", "float32" ] } } }jsonschema-0.13.0/fixtures/oneof.json000066400000000000000000000036731473503324600176170ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/root-one-of", "$ref": "#/$defs/RootOneOf", "$defs": { "ChildOneOf": { "oneOf": [ { "required": [ "child1", "child4" ], "title": "group1" }, { "required": [ "child2", "child3" ], "title": "group2" } ], "properties": { "child1": { "type": "string" }, "child2": { "type": "string" }, "child3": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "child4": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "RootOneOf": { "oneOf": [ { "required": [ "field1", "field4" ], "title": "group1" }, { "required": [ "field2" ], "title": "group2" } ], "properties": { "field1": { "type": "string" }, "field2": { "type": "string" }, "field3": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "field4": { "type": "string" }, "child": { "$ref": "#/$defs/ChildOneOf" }, "field6": { "oneOf": [ { "$ref": "Outer" }, { "$ref": "OuterNamed" }, { "$ref": "OuterPtr" } ] } }, "additionalProperties": false, "type": "object" } } }jsonschema-0.13.0/fixtures/oneof_ref.json000066400000000000000000000023261473503324600204450ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/server", "$ref": "#/$defs/Server", "$defs": { "Server": { "properties": { "ip_address": { "oneOf": [ { "$ref": "#/$defs/ipv4" }, { "$ref": "#/$defs/ipv6" } ] }, "ip_addresses": { "items": { "oneOf": [ { "$ref": "#/$defs/ipv4" }, { "$ref": "#/$defs/ipv6" } ] }, "type": "array" }, "ip_address_any": { "anyOf": [ { "$ref": "#/$defs/ipv4" }, { "$ref": "#/$defs/ipv6" } ] }, "ip_addresses_any": { "items": { "anyOf": [ { "$ref": "#/$defs/ipv4" }, { "$ref": "#/$defs/ipv6" } ] }, "type": "array" } }, "additionalProperties": false, "type": "object" } } }jsonschema-0.13.0/fixtures/recursive.json000066400000000000000000000010451473503324600205070ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/recursive-example", "$ref": "#/$defs/RecursiveExample", "$defs": { "RecursiveExample": { "properties": { "text": { "type": "string" }, "children": { "items": { "$ref": "#/$defs/RecursiveExample" }, "type": "array" } }, "additionalProperties": false, "type": "object", "required": [ "text" ] } } }jsonschema-0.13.0/fixtures/required_from_jsontags.json000066400000000000000000000110201473503324600232450ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "SomeUntaggedBaseProperty", "id", "name", "photo", "photo2" ] } } }jsonschema-0.13.0/fixtures/schema_alias.json000066400000000000000000000006221473503324600211110ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/alias-object-b", "$ref": "#/$defs/AliasObjectA", "$defs": { "AliasObjectA": { "properties": { "prop_a": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "prop_a" ] } } }jsonschema-0.13.0/fixtures/schema_alias_2.json000066400000000000000000000011751473503324600213360ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/alias-object-c", "$ref": "#/$defs/AliasObjectC", "$defs": { "AliasObjectA": { "properties": { "prop_a": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "prop_a" ] }, "AliasObjectC": { "properties": { "obj_b": { "$ref": "#/$defs/AliasObjectA" } }, "additionalProperties": false, "type": "object", "required": [ "obj_b" ] } } }jsonschema-0.13.0/fixtures/schema_property_alias.json000066400000000000000000000012411473503324600230530ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/alias-property-object-base", "$ref": "#/$defs/AliasPropertyObjectBase", "$defs": { "AliasObjectA": { "properties": { "prop_a": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "prop_a" ] }, "AliasPropertyObjectBase": { "properties": { "object": { "$ref": "#/$defs/AliasObjectA" } }, "additionalProperties": false, "type": "object", "required": [ "object" ] } } }jsonschema-0.13.0/fixtures/schema_with_expression.json000066400000000000000000000006501473503324600232530ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/expression", "$ref": "#/$defs/Expression", "$defs": { "Expression": { "properties": { "value": { "type": "integer", "foo": "bar=='baz'" } }, "additionalProperties": false, "type": "object", "required": [ "value" ] } } }jsonschema-0.13.0/fixtures/schema_with_minimum.json000066400000000000000000000006361473503324600225330ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/min-value", "$ref": "#/$defs/MinValue", "$defs": { "MinValue": { "properties": { "value4": { "type": "integer", "minimum": 0 } }, "additionalProperties": false, "type": "object", "required": [ "value4" ] } } }jsonschema-0.13.0/fixtures/test_config.json000066400000000000000000000007051473503324600210060ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/config", "$ref": "#/$defs/Config", "$defs": { "Config": { "properties": { "name": { "type": "string" }, "count": { "type": "integer" } }, "additionalProperties": false, "type": "object", "required": [ "name", "count" ] } } }jsonschema-0.13.0/fixtures/test_description_override.json000066400000000000000000000014271473503324600237650ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-description-override", "$ref": "#/$defs/TestDescriptionOverride", "$defs": { "TestDescriptionOverride": { "properties": { "FirstName": { "type": "string", "description": "test2" }, "LastName": { "type": "string", "description": "test3" }, "age": { "type": "integer", "description": "test4" }, "middle_name": { "type": "string", "description": "test5" } }, "additionalProperties": false, "type": "object", "required": [ "FirstName", "LastName", "age" ] } } }jsonschema-0.13.0/fixtures/test_user.json000066400000000000000000000114071473503324600205200ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "photo", "photo2", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.13.0/fixtures/test_user_assign_anchor.json000066400000000000000000000115101473503324600234110ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "$anchor": "GrandfatherType", "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "$anchor": "TestUser", "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "additionalProperties": { "type": "string" }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": 121, "minimum": 18, "exclusiveMinimum": 17 }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1.0, 1.5, 2.0 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "photo", "photo2", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.13.0/fixtures/test_yaml_and_json_prefer_yaml.json000066400000000000000000000011711473503324600247410ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/test-yaml-and-json", "$ref": "#/$defs/TestYamlAndJson", "$defs": { "TestYamlAndJson": { "properties": { "first_name": { "type": "string" }, "LastName": { "type": "string" }, "age": { "type": "integer" }, "middle_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "first_name", "LastName", "age" ] } } }jsonschema-0.13.0/fixtures/unsigned_int_handling.json000066400000000000000000000017721473503324600230410ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/unsigned-int-handler", "$ref": "#/$defs/UnsignedIntHandler", "$defs": { "UnsignedIntHandler": { "properties": { "min_len": { "items": { "type": "string", "minLength": 0 }, "type": "array" }, "max_len": { "items": { "type": "string", "maxLength": 0 }, "type": "array" }, "min_items": { "items": { "type": "string" }, "type": "array", "minItems": 0 }, "max_items": { "items": { "type": "string" }, "type": "array", "maxItems": 0 } }, "additionalProperties": false, "type": "object", "required": [ "min_len", "max_len", "min_items", "max_items" ] } } }jsonschema-0.13.0/fixtures/user_with_anchor.json000066400000000000000000000006611473503324600220460ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/user-with-anchor", "$ref": "#/$defs/UserWithAnchor", "$defs": { "UserWithAnchor": { "properties": { "name": { "$anchor": "Name", "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] } } }jsonschema-0.13.0/fixtures/with_custom_format.json000066400000000000000000000012331473503324600224140ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/invopop/jsonschema/with-custom-format", "$ref": "#/$defs/WithCustomFormat", "$defs": { "WithCustomFormat": { "properties": { "dates": { "items": { "type": "string", "format": "date" }, "type": "array" }, "odds": { "items": { "type": "string", "format": "odd" }, "type": "array" } }, "additionalProperties": false, "type": "object", "required": [ "dates", "odds" ] } } }jsonschema-0.13.0/fixtures/yaml_inline.json000066400000000000000000000011541473503324600210010ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/TestYamlInline", "definitions": { "Inner": { "required": ["foo"], "properties": { "foo": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "TestYamlInline": { "required": [ "Inlined" ], "properties": { "Inlined": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Inner" } }, "additionalProperties": false, "type": "object" } } } jsonschema-0.13.0/go.mod000066400000000000000000000006571473503324600150520ustar00rootroot00000000000000module github.com/invopop/jsonschema go 1.18 require ( github.com/stretchr/testify v1.8.1 github.com/wk8/go-ordered-map/v2 v2.1.8 ) require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) jsonschema-0.13.0/go.sum000066400000000000000000000044161473503324600150740ustar00rootroot00000000000000github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= jsonschema-0.13.0/id.go000066400000000000000000000033071473503324600146620ustar00rootroot00000000000000package jsonschema import ( "errors" "fmt" "net/url" "strings" ) // ID represents a Schema ID type which should always be a URI. // See draft-bhutton-json-schema-00 section 8.2.1 type ID string // EmptyID is used to explicitly define an ID with no value. const EmptyID ID = "" // Validate is used to check if the ID looks like a proper schema. // This is done by parsing the ID as a URL and checking it has all the // relevant parts. func (id ID) Validate() error { u, err := url.Parse(id.String()) if err != nil { return fmt.Errorf("invalid URL: %w", err) } if u.Hostname() == "" { return errors.New("missing hostname") } if !strings.Contains(u.Hostname(), ".") { return errors.New("hostname does not look valid") } if u.Path == "" { return errors.New("path is expected") } if u.Scheme != "https" && u.Scheme != "http" { return errors.New("unexpected schema") } return nil } // Anchor sets the anchor part of the schema URI. func (id ID) Anchor(name string) ID { b := id.Base() return ID(b.String() + "#" + name) } // Def adds or replaces a definition identifier. func (id ID) Def(name string) ID { b := id.Base() return ID(b.String() + "#/$defs/" + name) } // Add appends the provided path to the id, and removes any // anchor data that might be there. func (id ID) Add(path string) ID { b := id.Base() if !strings.HasPrefix(path, "/") { path = "/" + path } return ID(b.String() + path) } // Base removes any anchor information from the schema func (id ID) Base() ID { s := id.String() i := strings.LastIndex(s, "#") if i != -1 { s = s[0:i] } s = strings.TrimRight(s, "/") return ID(s) } // String provides string version of ID func (id ID) String() string { return string(id) } jsonschema-0.13.0/id_test.go000066400000000000000000000024271473503324600157230ustar00rootroot00000000000000package jsonschema_test import ( "testing" "github.com/invopop/jsonschema" "github.com/stretchr/testify/assert" ) func TestID(t *testing.T) { base := "https://invopop.com/schema" id := jsonschema.ID(base) assert.Equal(t, base, id.String()) id = id.Add("user") assert.EqualValues(t, base+"/user", id) id = id.Anchor("Name") assert.EqualValues(t, base+"/user#Name", id) id = id.Anchor("Title") assert.EqualValues(t, base+"/user#Title", id) id = id.Def("Name") assert.EqualValues(t, base+"/user#/$defs/Name", id) } func TestIDValidation(t *testing.T) { id := jsonschema.ID("https://invopop.com/schema/user") assert.NoError(t, id.Validate()) id = "https://encoding/json" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "hostname does not look valid") } id = "time" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "hostname") } id = "http://invopop.com" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "path") } id = "foor://invopop.com/schema/user" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "schema") } id = "invopop.com\n/test" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "invalid URL") } } jsonschema-0.13.0/reflect.go000066400000000000000000000726441473503324600157240ustar00rootroot00000000000000// Package jsonschema uses reflection to generate JSON Schemas from Go types [1]. // // If json tags are present on struct fields, they will be used to infer // property names and if a property is required (omitempty is present). // // [1] http://json-schema.org/latest/json-schema-validation.html package jsonschema import ( "bytes" "encoding/json" "net" "net/url" "reflect" "strconv" "strings" "time" ) // customSchemaImpl is used to detect if the type provides it's own // custom Schema Type definition to use instead. Very useful for situations // where there are custom JSON Marshal and Unmarshal methods. type customSchemaImpl interface { JSONSchema() *Schema } // Function to be run after the schema has been generated. // this will let you modify a schema afterwards type extendSchemaImpl interface { JSONSchemaExtend(*Schema) } // If the object to be reflected defines a `JSONSchemaAlias` method, its type will // be used instead of the original type. type aliasSchemaImpl interface { JSONSchemaAlias() any } // If an object to be reflected defines a `JSONSchemaPropertyAlias` method, // it will be called for each property to determine if another object // should be used for the contents. type propertyAliasSchemaImpl interface { JSONSchemaProperty(prop string) any } var customAliasSchema = reflect.TypeOf((*aliasSchemaImpl)(nil)).Elem() var customPropertyAliasSchema = reflect.TypeOf((*propertyAliasSchemaImpl)(nil)).Elem() var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem() var extendType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem() // customSchemaGetFieldDocString type customSchemaGetFieldDocString interface { GetFieldDocString(fieldName string) string } type customGetFieldDocString func(fieldName string) string var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem() // Reflect reflects to Schema from a value using the default Reflector func Reflect(v any) *Schema { return ReflectFromType(reflect.TypeOf(v)) } // ReflectFromType generates root schema using the default Reflector func ReflectFromType(t reflect.Type) *Schema { r := &Reflector{} return r.ReflectFromType(t) } // A Reflector reflects values into a Schema. type Reflector struct { // BaseSchemaID defines the URI that will be used as a base to determine Schema // IDs for models. For example, a base Schema ID of `https://invopop.com/schemas` // when defined with a struct called `User{}`, will result in a schema with an // ID set to `https://invopop.com/schemas/user`. // // If no `BaseSchemaID` is provided, we'll take the type's complete package path // and use that as a base instead. Set `Anonymous` to try if you do not want to // include a schema ID. BaseSchemaID ID // Anonymous when true will hide the auto-generated Schema ID and provide what is // known as an "anonymous schema". As a rule, this is not recommended. Anonymous bool // AssignAnchor when true will use the original struct's name as an anchor inside // every definition, including the root schema. These can be useful for having a // reference to the original struct's name in CamelCase instead of the snake-case used // by default for URI compatibility. // // Anchors do not appear to be widely used out in the wild, so at this time the // anchors themselves will not be used inside generated schema. AssignAnchor bool // AllowAdditionalProperties will cause the Reflector to generate a schema // without additionalProperties set to 'false' for all struct types. This means // the presence of additional keys in JSON objects will not cause validation // to fail. Note said additional keys will simply be dropped when the // validated JSON is unmarshaled. AllowAdditionalProperties bool // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema // that requires any key tagged with `jsonschema:required`, overriding the // default of requiring any key *not* tagged with `json:,omitempty`. RequiredFromJSONSchemaTags bool // Do not reference definitions. This will remove the top-level $defs map and // instead cause the entire structure of types to be output in one tree. The // list of type definitions (`$defs`) will not be included. DoNotReference bool // ExpandedStruct when true will include the reflected type's definition in the // root as opposed to a definition with a reference. ExpandedStruct bool // FieldNameTag will change the tag used to get field names. json tags are used by default. FieldNameTag string // IgnoredTypes defines a slice of types that should be ignored in the schema, // switching to just allowing additional properties instead. IgnoredTypes []any // Lookup allows a function to be defined that will provide a custom mapping of // types to Schema IDs. This allows existing schema documents to be referenced // by their ID instead of being embedded into the current schema definitions. // Reflected types will never be pointers, only underlying elements. Lookup func(reflect.Type) ID // Mapper is a function that can be used to map custom Go types to jsonschema schemas. Mapper func(reflect.Type) *Schema // Namer allows customizing of type names. The default is to use the type's name // provided by the reflect package. Namer func(reflect.Type) string // KeyNamer allows customizing of key names. // The default is to use the key's name as is, or the json tag if present. // If a json tag is present, KeyNamer will receive the tag's name as an argument, not the original key name. KeyNamer func(string) string // AdditionalFields allows adding structfields for a given type AdditionalFields func(reflect.Type) []reflect.StructField // LookupComment allows customizing comment lookup. Given a reflect.Type and optionally // a field name, it should return the comment string associated with this type or field. // // If the field name is empty, it should return the type's comment; otherwise, the field's // comment should be returned. If no comment is found, an empty string should be returned. // // When set, this function is called before the below CommentMap lookup mechanism. However, // if it returns an empty string, the CommentMap is still consulted. LookupComment func(reflect.Type, string) string // CommentMap is a dictionary of fully qualified go types and fields to comment // strings that will be used if a description has not already been provided in // the tags. Types and fields are added to the package path using "." as a // separator. // // Type descriptions should be defined like: // // map[string]string{"github.com/invopop/jsonschema.Reflector": "A Reflector reflects values into a Schema."} // // And Fields defined as: // // map[string]string{"github.com/invopop/jsonschema.Reflector.DoNotReference": "Do not reference definitions."} // // See also: AddGoComments, LookupComment CommentMap map[string]string } // Reflect reflects to Schema from a value. func (r *Reflector) Reflect(v any) *Schema { return r.ReflectFromType(reflect.TypeOf(v)) } // ReflectFromType generates root schema func (r *Reflector) ReflectFromType(t reflect.Type) *Schema { if t.Kind() == reflect.Ptr { t = t.Elem() // re-assign from pointer } name := r.typeName(t) s := new(Schema) definitions := Definitions{} s.Definitions = definitions bs := r.reflectTypeToSchemaWithID(definitions, t) if r.ExpandedStruct { *s = *definitions[name] delete(definitions, name) } else { *s = *bs } // Attempt to set the schema ID if !r.Anonymous && s.ID == EmptyID { baseSchemaID := r.BaseSchemaID if baseSchemaID == EmptyID { id := ID("https://" + t.PkgPath()) if err := id.Validate(); err == nil { // it's okay to silently ignore URL errors baseSchemaID = id } } if baseSchemaID != EmptyID { s.ID = baseSchemaID.Add(ToSnakeCase(name)) } } s.Version = Version if !r.DoNotReference { s.Definitions = definitions } return s } // Available Go defined types for JSON Schema Validation. // RFC draft-wright-json-schema-validation-00, section 7.3 var ( timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1 ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5 uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6 ) // Byte slices will be encoded as base64 var byteSliceType = reflect.TypeOf([]byte(nil)) // Except for json.RawMessage var rawMessageType = reflect.TypeOf(json.RawMessage{}) // Go code generated from protobuf enum types should fulfil this interface. type protoEnum interface { EnumDescriptor() ([]byte, []int) } var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem() // SetBaseSchemaID is a helper use to be able to set the reflectors base // schema ID from a string as opposed to then ID instance. func (r *Reflector) SetBaseSchemaID(id string) { r.BaseSchemaID = ID(id) } func (r *Reflector) refOrReflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { id := r.lookupID(t) if id != EmptyID { return &Schema{ Ref: id.String(), } } // Already added to definitions? if def := r.refDefinition(definitions, t); def != nil { return def } return r.reflectTypeToSchemaWithID(definitions, t) } func (r *Reflector) reflectTypeToSchemaWithID(defs Definitions, t reflect.Type) *Schema { s := r.reflectTypeToSchema(defs, t) if s != nil { if r.Lookup != nil { id := r.Lookup(t) if id != EmptyID { s.ID = id } } } return s } func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { // only try to reflect non-pointers if t.Kind() == reflect.Ptr { return r.refOrReflectTypeToSchema(definitions, t.Elem()) } // Check if the there is an alias method that provides an object // that we should use instead of this one. if t.Implements(customAliasSchema) { v := reflect.New(t) o := v.Interface().(aliasSchemaImpl) t = reflect.TypeOf(o.JSONSchemaAlias()) return r.refOrReflectTypeToSchema(definitions, t) } // Do any pre-definitions exist? if r.Mapper != nil { if t := r.Mapper(t); t != nil { return t } } if rt := r.reflectCustomSchema(definitions, t); rt != nil { return rt } // Prepare a base to which details can be added st := new(Schema) // jsonpb will marshal protobuf enum options as either strings or integers. // It will unmarshal either. if t.Implements(protoEnumType) { st.OneOf = []*Schema{ {Type: "string"}, {Type: "integer"}, } return st } // Defined format types for JSON Schema Validation // RFC draft-wright-json-schema-validation-00, section 7.3 // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7 if t == ipType { // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5 st.Type = "string" st.Format = "ipv4" return st } switch t.Kind() { case reflect.Struct: r.reflectStruct(definitions, t, st) case reflect.Slice, reflect.Array: r.reflectSliceOrArray(definitions, t, st) case reflect.Map: r.reflectMap(definitions, t, st) case reflect.Interface: // empty case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: st.Type = "integer" case reflect.Float32, reflect.Float64: st.Type = "number" case reflect.Bool: st.Type = "boolean" case reflect.String: st.Type = "string" default: panic("unsupported type " + t.String()) } r.reflectSchemaExtend(definitions, t, st) // Always try to reference the definition which may have just been created if def := r.refDefinition(definitions, t); def != nil { return def } return st } func (r *Reflector) reflectCustomSchema(definitions Definitions, t reflect.Type) *Schema { if t.Kind() == reflect.Ptr { return r.reflectCustomSchema(definitions, t.Elem()) } if t.Implements(customType) { v := reflect.New(t) o := v.Interface().(customSchemaImpl) st := o.JSONSchema() r.addDefinition(definitions, t, st) if ref := r.refDefinition(definitions, t); ref != nil { return ref } return st } return nil } func (r *Reflector) reflectSchemaExtend(definitions Definitions, t reflect.Type, s *Schema) *Schema { if t.Implements(extendType) { v := reflect.New(t) o := v.Interface().(extendSchemaImpl) o.JSONSchemaExtend(s) if ref := r.refDefinition(definitions, t); ref != nil { return ref } } return s } func (r *Reflector) reflectSliceOrArray(definitions Definitions, t reflect.Type, st *Schema) { if t == rawMessageType { return } r.addDefinition(definitions, t, st) if st.Description == "" { st.Description = r.lookupComment(t, "") } if t.Kind() == reflect.Array { l := uint64(t.Len()) st.MinItems = &l st.MaxItems = &l } if t.Kind() == reflect.Slice && t.Elem() == byteSliceType.Elem() { st.Type = "string" // NOTE: ContentMediaType is not set here st.ContentEncoding = "base64" } else { st.Type = "array" st.Items = r.refOrReflectTypeToSchema(definitions, t.Elem()) } } func (r *Reflector) reflectMap(definitions Definitions, t reflect.Type, st *Schema) { r.addDefinition(definitions, t, st) st.Type = "object" if st.Description == "" { st.Description = r.lookupComment(t, "") } switch t.Key().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: st.PatternProperties = map[string]*Schema{ "^[0-9]+$": r.refOrReflectTypeToSchema(definitions, t.Elem()), } st.AdditionalProperties = FalseSchema return } if t.Elem().Kind() != reflect.Interface { st.AdditionalProperties = r.refOrReflectTypeToSchema(definitions, t.Elem()) } } // Reflects a struct to a JSON Schema type. func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type, s *Schema) { // Handle special types switch t { case timeType: // date-time RFC section 7.3.1 s.Type = "string" s.Format = "date-time" return case uriType: // uri RFC section 7.3.6 s.Type = "string" s.Format = "uri" return } r.addDefinition(definitions, t, s) s.Type = "object" s.Properties = NewProperties() s.Description = r.lookupComment(t, "") if r.AssignAnchor { s.Anchor = t.Name() } if !r.AllowAdditionalProperties && s.AdditionalProperties == nil { s.AdditionalProperties = FalseSchema } ignored := false for _, it := range r.IgnoredTypes { if reflect.TypeOf(it) == t { ignored = true break } } if !ignored { r.reflectStructFields(s, definitions, t) } } func (r *Reflector) reflectStructFields(st *Schema, definitions Definitions, t reflect.Type) { if t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Struct { return } var getFieldDocString customGetFieldDocString if t.Implements(customStructGetFieldDocString) { v := reflect.New(t) o := v.Interface().(customSchemaGetFieldDocString) getFieldDocString = o.GetFieldDocString } customPropertyMethod := func(string) any { return nil } if t.Implements(customPropertyAliasSchema) { v := reflect.New(t) o := v.Interface().(propertyAliasSchemaImpl) customPropertyMethod = o.JSONSchemaProperty } handleField := func(f reflect.StructField) { name, shouldEmbed, required, nullable := r.reflectFieldName(f) // if anonymous and exported type should be processed recursively // current type should inherit properties of anonymous one if name == "" { if shouldEmbed { r.reflectStructFields(st, definitions, f.Type) } return } // If a JSONSchemaAlias(prop string) method is defined, attempt to use // the provided object's type instead of the field's type. var property *Schema if alias := customPropertyMethod(name); alias != nil { property = r.refOrReflectTypeToSchema(definitions, reflect.TypeOf(alias)) } else { property = r.refOrReflectTypeToSchema(definitions, f.Type) } property.structKeywordsFromTags(f, st, name) if property.Description == "" { property.Description = r.lookupComment(t, f.Name) } if getFieldDocString != nil { property.Description = getFieldDocString(f.Name) } if nullable { property = &Schema{ OneOf: []*Schema{ property, { Type: "null", }, }, } } st.Properties.Set(name, property) if required { st.Required = appendUniqueString(st.Required, name) } } for i := 0; i < t.NumField(); i++ { f := t.Field(i) handleField(f) } if r.AdditionalFields != nil { if af := r.AdditionalFields(t); af != nil { for _, sf := range af { handleField(sf) } } } } func appendUniqueString(base []string, value string) []string { for _, v := range base { if v == value { return base } } return append(base, value) } // addDefinition will append the provided schema. If needed, an ID and anchor will also be added. func (r *Reflector) addDefinition(definitions Definitions, t reflect.Type, s *Schema) { name := r.typeName(t) if name == "" { return } definitions[name] = s } // refDefinition will provide a schema with a reference to an existing definition. func (r *Reflector) refDefinition(definitions Definitions, t reflect.Type) *Schema { if r.DoNotReference { return nil } name := r.typeName(t) if name == "" { return nil } if _, ok := definitions[name]; !ok { return nil } return &Schema{ Ref: "#/$defs/" + name, } } func (r *Reflector) lookupID(t reflect.Type) ID { if r.Lookup != nil { if t.Kind() == reflect.Ptr { t = t.Elem() } return r.Lookup(t) } return EmptyID } func (t *Schema) structKeywordsFromTags(f reflect.StructField, parent *Schema, propertyName string) { t.Description = f.Tag.Get("jsonschema_description") tags := splitOnUnescapedCommas(f.Tag.Get("jsonschema")) tags = t.genericKeywords(tags, parent, propertyName) switch t.Type { case "string": t.stringKeywords(tags) case "number": t.numericalKeywords(tags) case "integer": t.numericalKeywords(tags) case "array": t.arrayKeywords(tags) case "boolean": t.booleanKeywords(tags) } extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",") t.extraKeywords(extras) } // read struct tags for generic keywords func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName string) []string { //nolint:gocyclo unprocessed := make([]string, 0, len(tags)) for _, tag := range tags { nameValue := strings.SplitN(tag, "=", 2) if len(nameValue) == 2 { name, val := nameValue[0], nameValue[1] switch name { case "title": t.Title = val case "description": t.Description = val case "type": t.Type = val case "anchor": t.Anchor = val case "oneof_required": var typeFound *Schema for i := range parent.OneOf { if parent.OneOf[i].Title == nameValue[1] { typeFound = parent.OneOf[i] } } if typeFound == nil { typeFound = &Schema{ Title: nameValue[1], Required: []string{}, } parent.OneOf = append(parent.OneOf, typeFound) } typeFound.Required = append(typeFound.Required, propertyName) case "anyof_required": var typeFound *Schema for i := range parent.AnyOf { if parent.AnyOf[i].Title == nameValue[1] { typeFound = parent.AnyOf[i] } } if typeFound == nil { typeFound = &Schema{ Title: nameValue[1], Required: []string{}, } parent.AnyOf = append(parent.AnyOf, typeFound) } typeFound.Required = append(typeFound.Required, propertyName) case "oneof_ref": subSchema := t if t.Items != nil { subSchema = t.Items } if subSchema.OneOf == nil { subSchema.OneOf = make([]*Schema, 0, 1) } subSchema.Ref = "" refs := strings.Split(nameValue[1], ";") for _, r := range refs { subSchema.OneOf = append(subSchema.OneOf, &Schema{ Ref: r, }) } case "oneof_type": if t.OneOf == nil { t.OneOf = make([]*Schema, 0, 1) } t.Type = "" types := strings.Split(nameValue[1], ";") for _, ty := range types { t.OneOf = append(t.OneOf, &Schema{ Type: ty, }) } case "anyof_ref": subSchema := t if t.Items != nil { subSchema = t.Items } if subSchema.AnyOf == nil { subSchema.AnyOf = make([]*Schema, 0, 1) } subSchema.Ref = "" refs := strings.Split(nameValue[1], ";") for _, r := range refs { subSchema.AnyOf = append(subSchema.AnyOf, &Schema{ Ref: r, }) } case "anyof_type": if t.AnyOf == nil { t.AnyOf = make([]*Schema, 0, 1) } t.Type = "" types := strings.Split(nameValue[1], ";") for _, ty := range types { t.AnyOf = append(t.AnyOf, &Schema{ Type: ty, }) } default: unprocessed = append(unprocessed, tag) } } } return unprocessed } // read struct tags for boolean type keywords func (t *Schema) booleanKeywords(tags []string) { for _, tag := range tags { nameValue := strings.Split(tag, "=") if len(nameValue) != 2 { continue } name, val := nameValue[0], nameValue[1] if name == "default" { if val == "true" { t.Default = true } else if val == "false" { t.Default = false } } } } // read struct tags for string type keywords func (t *Schema) stringKeywords(tags []string) { for _, tag := range tags { nameValue := strings.SplitN(tag, "=", 2) if len(nameValue) == 2 { name, val := nameValue[0], nameValue[1] switch name { case "minLength": t.MinLength = parseUint(val) case "maxLength": t.MaxLength = parseUint(val) case "pattern": t.Pattern = val case "format": t.Format = val case "readOnly": i, _ := strconv.ParseBool(val) t.ReadOnly = i case "writeOnly": i, _ := strconv.ParseBool(val) t.WriteOnly = i case "default": t.Default = val case "example": t.Examples = append(t.Examples, val) case "enum": t.Enum = append(t.Enum, val) } } } } // read struct tags for numerical type keywords func (t *Schema) numericalKeywords(tags []string) { for _, tag := range tags { nameValue := strings.Split(tag, "=") if len(nameValue) == 2 { name, val := nameValue[0], nameValue[1] switch name { case "multipleOf": t.MultipleOf, _ = toJSONNumber(val) case "minimum": t.Minimum, _ = toJSONNumber(val) case "maximum": t.Maximum, _ = toJSONNumber(val) case "exclusiveMaximum": t.ExclusiveMaximum, _ = toJSONNumber(val) case "exclusiveMinimum": t.ExclusiveMinimum, _ = toJSONNumber(val) case "default": if num, ok := toJSONNumber(val); ok { t.Default = num } case "example": if num, ok := toJSONNumber(val); ok { t.Examples = append(t.Examples, num) } case "enum": if num, ok := toJSONNumber(val); ok { t.Enum = append(t.Enum, num) } } } } } // read struct tags for object type keywords // func (t *Type) objectKeywords(tags []string) { // for _, tag := range tags{ // nameValue := strings.Split(tag, "=") // name, val := nameValue[0], nameValue[1] // switch name{ // case "dependencies": // t.Dependencies = val // break; // case "patternProperties": // t.PatternProperties = val // break; // } // } // } // read struct tags for array type keywords func (t *Schema) arrayKeywords(tags []string) { var defaultValues []any unprocessed := make([]string, 0, len(tags)) for _, tag := range tags { nameValue := strings.Split(tag, "=") if len(nameValue) == 2 { name, val := nameValue[0], nameValue[1] switch name { case "minItems": t.MinItems = parseUint(val) case "maxItems": t.MaxItems = parseUint(val) case "uniqueItems": t.UniqueItems = true case "default": defaultValues = append(defaultValues, val) case "format": t.Items.Format = val case "pattern": t.Items.Pattern = val default: unprocessed = append(unprocessed, tag) // left for further processing by underlying type } } } if len(defaultValues) > 0 { t.Default = defaultValues } if len(unprocessed) == 0 { // we don't have anything else to process return } switch t.Items.Type { case "string": t.Items.stringKeywords(unprocessed) case "number": t.Items.numericalKeywords(unprocessed) case "integer": t.Items.numericalKeywords(unprocessed) case "array": // explicitly don't support traversal for the [][]..., as it's unclear where the array tags belong case "boolean": t.Items.booleanKeywords(unprocessed) } } func (t *Schema) extraKeywords(tags []string) { for _, tag := range tags { nameValue := strings.SplitN(tag, "=", 2) if len(nameValue) == 2 { t.setExtra(nameValue[0], nameValue[1]) } } } func (t *Schema) setExtra(key, val string) { if t.Extras == nil { t.Extras = map[string]any{} } if existingVal, ok := t.Extras[key]; ok { switch existingVal := existingVal.(type) { case string: t.Extras[key] = []string{existingVal, val} case []string: t.Extras[key] = append(existingVal, val) case int: t.Extras[key], _ = strconv.Atoi(val) case bool: t.Extras[key] = (val == "true" || val == "t") } } else { switch key { case "minimum": t.Extras[key], _ = strconv.Atoi(val) default: var x any if val == "true" { x = true } else if val == "false" { x = false } else { x = val } t.Extras[key] = x } } } func requiredFromJSONTags(tags []string, val *bool) { if ignoredByJSONTags(tags) { return } for _, tag := range tags[1:] { if tag == "omitempty" { *val = false return } } *val = true } func requiredFromJSONSchemaTags(tags []string, val *bool) { if ignoredByJSONSchemaTags(tags) { return } for _, tag := range tags { if tag == "required" { *val = true } } } func nullableFromJSONSchemaTags(tags []string) bool { if ignoredByJSONSchemaTags(tags) { return false } for _, tag := range tags { if tag == "nullable" { return true } } return false } func ignoredByJSONTags(tags []string) bool { return tags[0] == "-" } func ignoredByJSONSchemaTags(tags []string) bool { return tags[0] == "-" } func inlinedByJSONTags(tags []string) bool { for _, tag := range tags[1:] { if tag == "inline" { return true } } return false } // toJSONNumber converts string to *json.Number. // It'll aso return whether the number is valid. func toJSONNumber(s string) (json.Number, bool) { num := json.Number(s) if _, err := num.Int64(); err == nil { return num, true } if _, err := num.Float64(); err == nil { return num, true } return json.Number(""), false } func parseUint(num string) *uint64 { val, err := strconv.ParseUint(num, 10, 64) if err != nil { return nil } return &val } func (r *Reflector) fieldNameTag() string { if r.FieldNameTag != "" { return r.FieldNameTag } return "json" } func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool, bool) { jsonTagString := f.Tag.Get(r.fieldNameTag()) jsonTags := strings.Split(jsonTagString, ",") if ignoredByJSONTags(jsonTags) { return "", false, false, false } schemaTags := strings.Split(f.Tag.Get("jsonschema"), ",") if ignoredByJSONSchemaTags(schemaTags) { return "", false, false, false } var required bool if !r.RequiredFromJSONSchemaTags { requiredFromJSONTags(jsonTags, &required) } requiredFromJSONSchemaTags(schemaTags, &required) nullable := nullableFromJSONSchemaTags(schemaTags) if f.Anonymous && jsonTags[0] == "" { // As per JSON Marshal rules, anonymous structs are inherited if f.Type.Kind() == reflect.Struct { return "", true, false, false } // As per JSON Marshal rules, anonymous pointer to structs are inherited if f.Type.Kind() == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct { return "", true, false, false } } // As per JSON Marshal rules, inline nested structs that have `inline` tag. if inlinedByJSONTags(jsonTags) { return "", true, false, false } // Try to determine the name from the different combos name := f.Name if jsonTags[0] != "" { name = jsonTags[0] } if !f.Anonymous && f.PkgPath != "" { // field not anonymous and not export has no export name name = "" } else if r.KeyNamer != nil { name = r.KeyNamer(name) } return name, false, required, nullable } // UnmarshalJSON is used to parse a schema object or boolean. func (t *Schema) UnmarshalJSON(data []byte) error { if bytes.Equal(data, []byte("true")) { *t = *TrueSchema return nil } else if bytes.Equal(data, []byte("false")) { *t = *FalseSchema return nil } type SchemaAlt Schema aux := &struct { *SchemaAlt }{ SchemaAlt: (*SchemaAlt)(t), } return json.Unmarshal(data, aux) } // MarshalJSON is used to serialize a schema object or boolean. func (t *Schema) MarshalJSON() ([]byte, error) { if t.boolean != nil { if *t.boolean { return []byte("true"), nil } return []byte("false"), nil } if reflect.DeepEqual(&Schema{}, t) { // Don't bother returning empty schemas return []byte("true"), nil } type SchemaAlt Schema b, err := json.Marshal((*SchemaAlt)(t)) if err != nil { return nil, err } if len(t.Extras) == 0 { return b, nil } m, err := json.Marshal(t.Extras) if err != nil { return nil, err } if len(b) == 2 { return m, nil } b[len(b)-1] = ',' return append(b, m[1:]...), nil } func (r *Reflector) typeName(t reflect.Type) string { if r.Namer != nil { if name := r.Namer(t); name != "" { return name } } return t.Name() } // Split on commas that are not preceded by `\`. // This way, we prevent splitting regexes func splitOnUnescapedCommas(tagString string) []string { ret := make([]string, 0) separated := strings.Split(tagString, ",") ret = append(ret, separated[0]) i := 0 for _, nextTag := range separated[1:] { if len(ret[i]) == 0 { ret = append(ret, nextTag) i++ continue } if ret[i][len(ret[i])-1] == '\\' { ret[i] = ret[i][:len(ret[i])-1] + "," + nextTag } else { ret = append(ret, nextTag) i++ } } return ret } func fullyQualifiedTypeName(t reflect.Type) string { return t.PkgPath() + "." + t.Name() } jsonschema-0.13.0/reflect_comments.go000066400000000000000000000072531473503324600176230ustar00rootroot00000000000000package jsonschema import ( "fmt" "io/fs" gopath "path" "path/filepath" "reflect" "strings" "go/ast" "go/doc" "go/parser" "go/token" ) type commentOptions struct { fullObjectText bool // use the first sentence only? } // CommentOption allows for special configuration options when preparing Go // source files for comment extraction. type CommentOption func(*commentOptions) // WithFullComment will configure the comment extraction to process to use an // object type's full comment text instead of just the synopsis. func WithFullComment() CommentOption { return func(o *commentOptions) { o.fullObjectText = true } } // AddGoComments will update the reflectors comment map with all the comments // found in the provided source directories including sub-directories, in order to // generate a dictionary of comments associated with Types and Fields. The results // will be added to the `Reflect.CommentMap` ready to use with Schema "description" // fields. // // The `go/parser` library is used to extract all the comments and unfortunately doesn't // have a built-in way to determine the fully qualified name of a package. The `base` // parameter, the URL used to import that package, is thus required to be able to match // reflected types. // // When parsing type comments, by default we use the `go/doc`'s Synopsis method to extract // the first phrase only. Field comments, which tend to be much shorter, will include everything. // This behavior can be changed by using the `WithFullComment` option. func (r *Reflector) AddGoComments(base, path string, opts ...CommentOption) error { if r.CommentMap == nil { r.CommentMap = make(map[string]string) } co := new(commentOptions) for _, opt := range opts { opt(co) } return r.extractGoComments(base, path, r.CommentMap, co) } func (r *Reflector) extractGoComments(base, path string, commentMap map[string]string, opts *commentOptions) error { fset := token.NewFileSet() dict := make(map[string][]*ast.Package) err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { d, err := parser.ParseDir(fset, path, nil, parser.ParseComments) if err != nil { return err } for _, v := range d { // paths may have multiple packages, like for tests k := gopath.Join(base, path) dict[k] = append(dict[k], v) } } return nil }) if err != nil { return err } for pkg, p := range dict { for _, f := range p { gtxt := "" typ := "" ast.Inspect(f, func(n ast.Node) bool { switch x := n.(type) { case *ast.TypeSpec: typ = x.Name.String() if !ast.IsExported(typ) { typ = "" } else { txt := x.Doc.Text() if txt == "" && gtxt != "" { txt = gtxt gtxt = "" } if !opts.fullObjectText { txt = doc.Synopsis(txt) } commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt) } case *ast.Field: txt := x.Doc.Text() if txt == "" { txt = x.Comment.Text() } if typ != "" && txt != "" { for _, n := range x.Names { if ast.IsExported(n.String()) { k := fmt.Sprintf("%s.%s.%s", pkg, typ, n) commentMap[k] = strings.TrimSpace(txt) } } } case *ast.GenDecl: // remember for the next type gtxt = x.Doc.Text() } return true }) } } return nil } func (r *Reflector) lookupComment(t reflect.Type, name string) string { if r.LookupComment != nil { if comment := r.LookupComment(t, name); comment != "" { return comment } } if r.CommentMap == nil { return "" } n := fullyQualifiedTypeName(t) if name != "" { n = n + "." + name } return r.CommentMap[n] } jsonschema-0.13.0/reflect_comments_test.go000066400000000000000000000034051473503324600206550ustar00rootroot00000000000000package jsonschema import ( "fmt" "path/filepath" "reflect" "strings" "testing" "github.com/stretchr/testify/require" "github.com/invopop/jsonschema/examples" ) func TestCommentsSchemaGeneration(t *testing.T) { tests := []struct { typ any reflector *Reflector fixture string }{ {&examples.User{}, prepareCommentReflector(t), "fixtures/go_comments.json"}, {&examples.User{}, prepareCommentReflector(t, WithFullComment()), "fixtures/go_comments_full.json"}, {&examples.User{}, prepareCustomCommentReflector(t), "fixtures/custom_comments.json"}, } for _, tt := range tests { name := strings.TrimSuffix(filepath.Base(tt.fixture), ".json") t.Run(name, func(t *testing.T) { compareSchemaOutput(t, tt.fixture, tt.reflector, tt.typ, ) }) } } func prepareCommentReflector(t *testing.T, opts ...CommentOption) *Reflector { t.Helper() r := new(Reflector) err := r.AddGoComments("github.com/invopop/jsonschema", "./examples", opts...) require.NoError(t, err, "did not expect error while adding comments") return r } func prepareCustomCommentReflector(t *testing.T) *Reflector { t.Helper() r := new(Reflector) r.LookupComment = func(t reflect.Type, f string) string { if t != reflect.TypeOf(examples.User{}) { // To test the interaction between a custom LookupComment function and the // AddGoComments function, we only override comments for the User type. return "" } if f == "" { return fmt.Sprintf("Go type %s, defined in package %s.", t.Name(), t.PkgPath()) } return fmt.Sprintf("Field %s of Go type %s.%s.", f, t.PkgPath(), t.Name()) } // Also add the Go comments. err := r.AddGoComments("github.com/invopop/jsonschema", "./examples") require.NoError(t, err, "did not expect error while adding comments") return r } jsonschema-0.13.0/reflect_test.go000066400000000000000000000501331473503324600167500ustar00rootroot00000000000000package jsonschema import ( "encoding/json" "flag" "fmt" "net" "net/url" "os" "path/filepath" "reflect" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var updateFixtures = flag.Bool("update", false, "set to update fixtures") var compareFixtures = flag.Bool("compare", false, "output failed fixtures with .out.json") type GrandfatherType struct { FamilyName string `json:"family_name" jsonschema:"required"` } type SomeBaseType struct { ID string `json:"id"` // to test composition override SomeBaseProperty int `json:"some_base_property"` // The jsonschema required tag is nonsensical for private and ignored properties. // Their presence here tests that the fields *will not* be required in the output // schema, even if they are tagged required. somePrivateBaseProperty string `jsonschema:"required"` //nolint:unused SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` SomeSchemaIgnoredProperty string `jsonschema:"-,required"` Grandfather GrandfatherType `json:"grand"` SomeUntaggedBaseProperty bool `jsonschema:"required"` someUnexportedUntaggedBaseProperty bool //nolint:unused } type MapType map[string]any type ArrayType []string type nonExported struct { PublicNonExported int privateNonExported int // nolint:unused } type ProtoEnum int32 func (ProtoEnum) EnumDescriptor() ([]byte, []int) { return []byte(nil), []int{0} } const ( Unset ProtoEnum = iota Great ) type TestUser struct { SomeBaseType nonExported MapType ID int `json:"id" jsonschema:"required,minimum=bad,maximum=bad,exclusiveMinimum=bad,exclusiveMaximum=bad,default=bad"` 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"` Password string `json:"password" jsonschema:"writeOnly=true"` Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` Tags map[string]string `json:"tags,omitempty"` Options map[string]any `json:"options,omitempty"` TestFlag bool TestFlagFalse bool `json:",omitempty" jsonschema:"default=false"` TestFlagTrue bool `json:",omitempty" jsonschema:"default=true"` IgnoredCounter int `json:"-"` // Tests for RFC draft-wright-json-schema-validation-00, section 7.3 BirthDate time.Time `json:"birth_date,omitempty"` Website url.URL `json:"website,omitempty"` IPAddress net.IP `json:"network_address,omitempty"` // Tests for RFC draft-wright-json-schema-hyperschema-00, section 4 Photo []byte `json:"photo,omitempty" jsonschema:"required"` Photo2 Bytes `json:"photo2,omitempty" jsonschema:"required"` // Tests for jsonpb enum support Feeling ProtoEnum `json:"feeling,omitempty"` Age int `json:"age" jsonschema:"minimum=18,maximum=120,exclusiveMaximum=121,exclusiveMinimum=17"` Email string `json:"email" jsonschema:"format=email"` UUID string `json:"uuid" jsonschema:"format=uuid"` // Test for "extras" support Baz string `jsonschema_extras:"foo=bar,hello=world,foo=bar1"` BoolExtra string `json:"bool_extra,omitempty" jsonschema_extras:"isTrue=true,isFalse=false"` // Tests for simple enum tags Color string `json:"color" jsonschema:"enum=red,enum=green,enum=blue"` Rank int `json:"rank,omitempty" jsonschema:"enum=1,enum=2,enum=3"` Multiplier float64 `json:"mult,omitempty" jsonschema:"enum=1.0,enum=1.5,enum=2.0"` // Tests for enum tags on slices Roles []string `json:"roles" jsonschema:"enum=admin,enum=moderator,enum=user"` Priorities []int `json:"priorities,omitempty" jsonschema:"enum=-1,enum=0,enum=1,enun=2"` Offsets []float64 `json:"offsets,omitempty" jsonschema:"enum=1.570796,enum=3.141592,enum=6.283185"` // Test for raw JSON Anything any `json:"anything,omitempty"` Raw json.RawMessage `json:"raw"` } type CustomTime time.Time type CustomTypeField struct { CreatedAt CustomTime } type CustomTimeWithInterface time.Time type CustomTypeFieldWithInterface struct { CreatedAt CustomTimeWithInterface } func (CustomTimeWithInterface) JSONSchema() *Schema { return &Schema{ Type: "string", Format: "date-time", } } type RootOneOf struct { Field1 string `json:"field1" jsonschema:"oneof_required=group1"` Field2 string `json:"field2" jsonschema:"oneof_required=group2"` Field3 any `json:"field3" jsonschema:"oneof_type=string;array"` Field4 string `json:"field4" jsonschema:"oneof_required=group1"` Field5 ChildOneOf `json:"child"` Field6 any `json:"field6" jsonschema:"oneof_ref=Outer;OuterNamed;OuterPtr"` } type ChildOneOf struct { Child1 string `json:"child1" jsonschema:"oneof_required=group1"` Child2 string `json:"child2" jsonschema:"oneof_required=group2"` Child3 any `json:"child3" jsonschema:"oneof_required=group2,oneof_type=string;array"` Child4 string `json:"child4" jsonschema:"oneof_required=group1"` } type RootAnyOf struct { Field1 string `json:"field1" jsonschema:"anyof_required=group1"` Field2 string `json:"field2" jsonschema:"anyof_required=group2"` Field3 any `json:"field3" jsonschema:"anyof_type=string;array"` Field4 string `json:"field4" jsonschema:"anyof_required=group1"` Field5 ChildAnyOf `json:"child"` } type ChildAnyOf struct { Child1 string `json:"child1" jsonschema:"anyof_required=group1"` Child2 string `json:"child2" jsonschema:"anyof_required=group2"` Child3 any `json:"child3" jsonschema:"anyof_required=group2,oneof_type=string;array"` Child4 string `json:"child4" jsonschema:"anyof_required=group1"` } type Text string type TextNamed string type Outer struct { TextNamed Text `json:",omitempty"` Inner } type OuterNamed struct { Text `json:"text,omitempty"` Inner `json:"inner"` } type OuterInlined struct { Text `json:"text,omitempty"` Inner `json:",inline"` } type OuterPtr struct { *Inner Text `json:",omitempty"` } type Inner struct { Foo string `yaml:"foo"` } type MinValue struct { Value int `json:"value4" jsonschema_extras:"minimum=0"` } type Bytes []byte type TestNullable struct { Child1 string `json:"child1" jsonschema:"nullable"` } type CompactDate struct { Year int Month int } type UserWithAnchor struct { Name string `json:"name" jsonschema:"anchor=Name"` } func (CompactDate) JSONSchema() *Schema { return &Schema{ Type: "string", Title: "Compact Date", Description: "Short date that only includes year and month", Pattern: "^[0-9]{4}-[0-1][0-9]$", } } type TestDescriptionOverride struct { FirstName string `json:"FirstName"` LastName string `json:"LastName"` Age uint `json:"age"` MiddleName string `json:"middle_name,omitempty"` } func (TestDescriptionOverride) GetFieldDocString(fieldName string) string { switch fieldName { case "FirstName": return "test2" case "LastName": return "test3" case "Age": return "test4" case "MiddleName": return "test5" default: return "" } } type LookupName struct { Given string `json:"first"` Surname string `json:"surname"` } type LookupUser struct { Name *LookupName `json:"name"` Alias string `json:"alias,omitempty"` } type CustomSliceOuter struct { Slice CustomSliceType `json:"slice"` } type CustomSliceType []string func (CustomSliceType) JSONSchema() *Schema { return &Schema{ OneOf: []*Schema{{ Type: "string", }, { Type: "array", Items: &Schema{ Type: "string", }, }}, } } type CustomMapType map[string]string func (CustomMapType) JSONSchema() *Schema { properties := NewProperties() properties.Set("key", &Schema{ Type: "string", }) properties.Set("value", &Schema{ Type: "string", }) return &Schema{ Type: "array", Items: &Schema{ Type: "object", Properties: properties, Required: []string{"key", "value"}, }, } } type CustomMapOuter struct { MyMap CustomMapType `json:"my_map"` } type PatternTest struct { WithPattern string `json:"with_pattern" jsonschema:"minLength=1,pattern=[0-9]{1\\,4},maxLength=50"` } type RecursiveExample struct { Text string `json:"text"` Child []*RecursiveExample `json:"children,omitempty"` } type KeyNamedNested struct { NestedNotRenamedProperty string NotRenamed string } type KeyNamed struct { ThisWasLeftAsIs string NotComingFromJSON bool `json:"coming_from_json_tag_not_renamed"` NestedNotRenamed KeyNamedNested `json:"nested_not_renamed"` UnicodeShenanigans string RenamedByComputation int `jsonschema_description:"Description was preserved"` } type SchemaExtendTestBase struct { FirstName string `json:"FirstName"` LastName string `json:"LastName"` Age uint `json:"age"` MiddleName string `json:"middle_name,omitempty"` } type SchemaExtendTest struct { SchemaExtendTestBase `json:",inline"` } func (SchemaExtendTest) JSONSchemaExtend(base *Schema) { base.Properties.Delete("FirstName") base.Properties.Delete("age") val, _ := base.Properties.Get("LastName") val.Description = "some extra words" base.Required = []string{"LastName"} } type Expression struct { Value int `json:"value" jsonschema_extras:"foo=bar=='baz'"` } type PatternEqualsTest struct { WithEquals string `jsonschema:"pattern=foo=bar"` WithEqualsAndCommas string `jsonschema:"pattern=foo\\,=bar"` } func TestReflector(t *testing.T) { r := new(Reflector) s := "http://example.com/schema" r.SetBaseSchemaID(s) assert.EqualValues(t, s, r.BaseSchemaID) } func TestReflectFromType(t *testing.T) { r := new(Reflector) tu := new(TestUser) typ := reflect.TypeOf(tu) s := r.ReflectFromType(typ) assert.EqualValues(t, "https://github.com/invopop/jsonschema/test-user", s.ID) x := struct { Test string }{ Test: "foo", } typ = reflect.TypeOf(x) s = r.Reflect(typ) assert.Empty(t, s.ID) } func TestSchemaGeneration(t *testing.T) { tests := []struct { typ any reflector *Reflector fixture string }{ {&TestUser{}, &Reflector{}, "fixtures/test_user.json"}, {&UserWithAnchor{}, &Reflector{}, "fixtures/user_with_anchor.json"}, {&TestUser{}, &Reflector{AssignAnchor: true}, "fixtures/test_user_assign_anchor.json"}, {&TestUser{}, &Reflector{AllowAdditionalProperties: true}, "fixtures/allow_additional_props.json"}, {&TestUser{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/required_from_jsontags.json"}, {&TestUser{}, &Reflector{ExpandedStruct: true}, "fixtures/defaults_expanded_toplevel.json"}, {&TestUser{}, &Reflector{IgnoredTypes: []any{GrandfatherType{}}}, "fixtures/ignore_type.json"}, {&TestUser{}, &Reflector{DoNotReference: true}, "fixtures/no_reference.json"}, {&TestUser{}, &Reflector{DoNotReference: true, AssignAnchor: true}, "fixtures/no_reference_anchor.json"}, {&RootOneOf{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/oneof.json"}, {&RootAnyOf{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/anyof.json"}, {&CustomTypeField{}, &Reflector{ Mapper: func(i reflect.Type) *Schema { if i == reflect.TypeOf(CustomTime{}) { return &Schema{ Type: "string", Format: "date-time", } } return nil }, }, "fixtures/custom_type.json"}, {LookupUser{}, &Reflector{BaseSchemaID: "https://example.com/schemas"}, "fixtures/base_schema_id.json"}, {LookupUser{}, &Reflector{ Lookup: func(i reflect.Type) ID { switch i { case reflect.TypeOf(LookupUser{}): return ID("https://example.com/schemas/lookup-user") case reflect.TypeOf(LookupName{}): return ID("https://example.com/schemas/lookup-name") } return EmptyID }, }, "fixtures/lookup.json"}, {&LookupUser{}, &Reflector{ BaseSchemaID: "https://example.com/schemas", ExpandedStruct: true, AssignAnchor: true, Lookup: func(i reflect.Type) ID { switch i { case reflect.TypeOf(LookupUser{}): return ID("https://example.com/schemas/lookup-user") case reflect.TypeOf(LookupName{}): return ID("https://example.com/schemas/lookup-name") } return EmptyID }, }, "fixtures/lookup_expanded.json"}, {&Outer{}, &Reflector{ExpandedStruct: true}, "fixtures/inlining_inheritance.json"}, {&OuterNamed{}, &Reflector{ExpandedStruct: true}, "fixtures/inlining_embedded.json"}, {&OuterNamed{}, &Reflector{ExpandedStruct: true, AssignAnchor: true}, "fixtures/inlining_embedded_anchored.json"}, {&OuterInlined{}, &Reflector{ExpandedStruct: true}, "fixtures/inlining_tag.json"}, {&OuterPtr{}, &Reflector{ExpandedStruct: true}, "fixtures/inlining_ptr.json"}, {&MinValue{}, &Reflector{}, "fixtures/schema_with_minimum.json"}, {&TestNullable{}, &Reflector{}, "fixtures/nullable.json"}, {&GrandfatherType{}, &Reflector{ AdditionalFields: func(_ reflect.Type) []reflect.StructField { return []reflect.StructField{ { Name: "Addr", Type: reflect.TypeOf((*net.IP)(nil)).Elem(), Tag: "json:\"ip_addr\"", Anonymous: false, }, } }, }, "fixtures/custom_additional.json"}, {&TestDescriptionOverride{}, &Reflector{}, "fixtures/test_description_override.json"}, {&CompactDate{}, &Reflector{}, "fixtures/compact_date.json"}, {&CustomSliceOuter{}, &Reflector{}, "fixtures/custom_slice_type.json"}, {&CustomMapOuter{}, &Reflector{}, "fixtures/custom_map_type.json"}, {&CustomTypeFieldWithInterface{}, &Reflector{}, "fixtures/custom_type_with_interface.json"}, {&PatternTest{}, &Reflector{}, "fixtures/commas_in_pattern.json"}, {&RecursiveExample{}, &Reflector{}, "fixtures/recursive.json"}, {&KeyNamed{}, &Reflector{ KeyNamer: func(s string) string { switch s { case "ThisWasLeftAsIs": fallthrough case "NotRenamed": fallthrough case "nested_not_renamed": return s case "coming_from_json_tag_not_renamed": return "coming_from_json_tag" case "NestedNotRenamed": return "nested-renamed" case "NestedNotRenamedProperty": return "nested-renamed-property" case "UnicodeShenanigans": return "✨unicode✨ s̸̥͝h̷̳͒e̴̜̽n̸̡̿a̷̘̔n̷̘͐i̶̫̐ǵ̶̯a̵̘͒n̷̮̾s̸̟̓" case "RenamedByComputation": return fmt.Sprintf("%.2f", float64(len(s))+1/137.0) } return "unknown case" }, }, "fixtures/keynamed.json"}, {MapType{}, &Reflector{}, "fixtures/map_type.json"}, {ArrayType{}, &Reflector{}, "fixtures/array_type.json"}, {SchemaExtendTest{}, &Reflector{}, "fixtures/custom_type_extend.json"}, {Expression{}, &Reflector{}, "fixtures/schema_with_expression.json"}, {PatternEqualsTest{}, &Reflector{}, "fixtures/equals_in_pattern.json"}, } for _, tt := range tests { name := strings.TrimSuffix(filepath.Base(tt.fixture), ".json") t.Run(name, func(t *testing.T) { compareSchemaOutput(t, tt.fixture, tt.reflector, tt.typ, ) }) } } func TestBaselineUnmarshal(t *testing.T) { r := &Reflector{} compareSchemaOutput(t, "fixtures/test_user.json", r, &TestUser{}) } func compareSchemaOutput(t *testing.T, f string, r *Reflector, obj any) { t.Helper() expectedJSON, err := os.ReadFile(f) require.NoError(t, err) actualSchema := r.Reflect(obj) actualJSON, _ := json.MarshalIndent(actualSchema, "", " ") //nolint:errchkjson if *updateFixtures { _ = os.WriteFile(f, actualJSON, 0600) } if !assert.JSONEq(t, string(expectedJSON), string(actualJSON)) { if *compareFixtures { _ = os.WriteFile(strings.TrimSuffix(f, ".json")+".out.json", actualJSON, 0600) } } } func fixtureContains(t *testing.T, f, s string) { t.Helper() b, err := os.ReadFile(f) require.NoError(t, err) assert.Contains(t, string(b), s) } func TestSplitOnUnescapedCommas(t *testing.T) { tests := []struct { strToSplit string expected []string }{ {`Hello,this,is\,a\,string,haha`, []string{`Hello`, `this`, `is,a,string`, `haha`}}, {`hello,no\\,split`, []string{`hello`, `no\,split`}}, {`string without commas`, []string{`string without commas`}}, {`ünicode,𐂄,Ж\,П,ᠳ`, []string{`ünicode`, `𐂄`, `Ж,П`, `ᠳ`}}, {`empty,,tag`, []string{`empty`, ``, `tag`}}, } for _, test := range tests { actual := splitOnUnescapedCommas(test.strToSplit) require.Equal(t, test.expected, actual) } } func TestArrayExtraTags(t *testing.T) { type URIArray struct { TestURIs []string `jsonschema:"type=array,format=uri,pattern=^https://.*"` } r := new(Reflector) schema := r.Reflect(&URIArray{}) d := schema.Definitions["URIArray"] require.NotNil(t, d) props := d.Properties require.NotNil(t, props) p, found := props.Get("TestURIs") require.True(t, found) pt := p.Items.Format require.Equal(t, pt, "uri") pt = p.Items.Pattern require.Equal(t, pt, "^https://.*") } func TestFieldNameTag(t *testing.T) { type Config struct { Name string `yaml:"name"` Count int `yaml:"count"` } r := Reflector{ FieldNameTag: "yaml", } compareSchemaOutput(t, "fixtures/test_config.json", &r, &Config{}) } func TestFieldOneOfRef(t *testing.T) { type Server struct { IPAddress any `json:"ip_address,omitempty" jsonschema:"oneof_ref=#/$defs/ipv4;#/$defs/ipv6"` IPAddresses []any `json:"ip_addresses,omitempty" jsonschema:"oneof_ref=#/$defs/ipv4;#/$defs/ipv6"` IPAddressAny any `json:"ip_address_any,omitempty" jsonschema:"anyof_ref=#/$defs/ipv4;#/$defs/ipv6"` IPAddressesAny []any `json:"ip_addresses_any,omitempty" jsonschema:"anyof_ref=#/$defs/ipv4;#/$defs/ipv6"` } r := &Reflector{} compareSchemaOutput(t, "fixtures/oneof_ref.json", r, &Server{}) } func TestNumberHandling(t *testing.T) { type NumberHandler struct { Int64 int64 `json:"int64" jsonschema:"default=12"` Float32 float32 `json:"float32" jsonschema:"default=12.5"` } r := &Reflector{} compareSchemaOutput(t, "fixtures/number_handling.json", r, &NumberHandler{}) fixtureContains(t, "fixtures/number_handling.json", `"default": 12`) fixtureContains(t, "fixtures/number_handling.json", `"default": 12.5`) } func TestArrayHandling(t *testing.T) { type ArrayHandler struct { MinLen []string `json:"min_len" jsonschema:"minLength=2,default=qwerty"` MinVal []float64 `json:"min_val" jsonschema:"minimum=2.5"` } r := &Reflector{} compareSchemaOutput(t, "fixtures/array_handling.json", r, &ArrayHandler{}) fixtureContains(t, "fixtures/array_handling.json", `"minLength": 2`) fixtureContains(t, "fixtures/array_handling.json", `"minimum": 2.5`) } func TestUnsignedIntHandling(t *testing.T) { type UnsignedIntHandler struct { MinLen []string `json:"min_len" jsonschema:"minLength=0"` MaxLen []string `json:"max_len" jsonschema:"maxLength=0"` MinItems []string `json:"min_items" jsonschema:"minItems=0"` MaxItems []string `json:"max_items" jsonschema:"maxItems=0"` } r := &Reflector{} compareSchemaOutput(t, "fixtures/unsigned_int_handling.json", r, &UnsignedIntHandler{}) fixtureContains(t, "fixtures/unsigned_int_handling.json", `"minLength": 0`) fixtureContains(t, "fixtures/unsigned_int_handling.json", `"maxLength": 0`) fixtureContains(t, "fixtures/unsigned_int_handling.json", `"minItems": 0`) fixtureContains(t, "fixtures/unsigned_int_handling.json", `"maxItems": 0`) } func TestJSONSchemaFormat(t *testing.T) { type WithCustomFormat struct { Dates []string `json:"dates" jsonschema:"format=date"` Odds []string `json:"odds" jsonschema:"format=odd"` } r := &Reflector{} compareSchemaOutput(t, "fixtures/with_custom_format.json", r, &WithCustomFormat{}) fixtureContains(t, "fixtures/with_custom_format.json", `"format": "date"`) fixtureContains(t, "fixtures/with_custom_format.json", `"format": "odd"`) } type AliasObjectA struct { PropA string `json:"prop_a"` } type AliasObjectB struct { PropB string `json:"prop_b"` } type AliasObjectC struct { ObjB *AliasObjectB `json:"obj_b"` } type AliasPropertyObjectBase struct { Object any `json:"object"` } func (AliasPropertyObjectBase) JSONSchemaProperty(prop string) any { if prop == "object" { return &AliasObjectA{} } return nil } func (AliasObjectB) JSONSchemaAlias() any { return AliasObjectA{} } func TestJSONSchemaProperty(t *testing.T) { r := &Reflector{} compareSchemaOutput(t, "fixtures/schema_property_alias.json", r, &AliasPropertyObjectBase{}) } func TestJSONSchemaAlias(t *testing.T) { r := &Reflector{} compareSchemaOutput(t, "fixtures/schema_alias.json", r, &AliasObjectB{}) compareSchemaOutput(t, "fixtures/schema_alias_2.json", r, &AliasObjectC{}) } jsonschema-0.13.0/schema.go000066400000000000000000000133771473503324600155360ustar00rootroot00000000000000package jsonschema import ( "encoding/json" orderedmap "github.com/wk8/go-ordered-map/v2" ) // Version is the JSON Schema version. var Version = "https://json-schema.org/draft/2020-12/schema" // Schema represents a JSON Schema object type. // RFC draft-bhutton-json-schema-00 section 4.3 type Schema struct { // RFC draft-bhutton-json-schema-00 Version string `json:"$schema,omitempty"` // section 8.1.1 ID ID `json:"$id,omitempty"` // section 8.2.1 Anchor string `json:"$anchor,omitempty"` // section 8.2.2 Ref string `json:"$ref,omitempty"` // section 8.2.3.1 DynamicRef string `json:"$dynamicRef,omitempty"` // section 8.2.3.2 Definitions Definitions `json:"$defs,omitempty"` // section 8.2.4 Comments string `json:"$comment,omitempty"` // section 8.3 // RFC draft-bhutton-json-schema-00 section 10.2.1 (Sub-schemas with logic) AllOf []*Schema `json:"allOf,omitempty"` // section 10.2.1.1 AnyOf []*Schema `json:"anyOf,omitempty"` // section 10.2.1.2 OneOf []*Schema `json:"oneOf,omitempty"` // section 10.2.1.3 Not *Schema `json:"not,omitempty"` // section 10.2.1.4 // RFC draft-bhutton-json-schema-00 section 10.2.2 (Apply sub-schemas conditionally) If *Schema `json:"if,omitempty"` // section 10.2.2.1 Then *Schema `json:"then,omitempty"` // section 10.2.2.2 Else *Schema `json:"else,omitempty"` // section 10.2.2.3 DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"` // section 10.2.2.4 // RFC draft-bhutton-json-schema-00 section 10.3.1 (arrays) PrefixItems []*Schema `json:"prefixItems,omitempty"` // section 10.3.1.1 Items *Schema `json:"items,omitempty"` // section 10.3.1.2 (replaces additionalItems) Contains *Schema `json:"contains,omitempty"` // section 10.3.1.3 // RFC draft-bhutton-json-schema-00 section 10.3.2 (sub-schemas) Properties *orderedmap.OrderedMap[string, *Schema] `json:"properties,omitempty"` // section 10.3.2.1 PatternProperties map[string]*Schema `json:"patternProperties,omitempty"` // section 10.3.2.2 AdditionalProperties *Schema `json:"additionalProperties,omitempty"` // section 10.3.2.3 PropertyNames *Schema `json:"propertyNames,omitempty"` // section 10.3.2.4 // RFC draft-bhutton-json-schema-validation-00, section 6 Type string `json:"type,omitempty"` // section 6.1.1 Enum []any `json:"enum,omitempty"` // section 6.1.2 Const any `json:"const,omitempty"` // section 6.1.3 MultipleOf json.Number `json:"multipleOf,omitempty"` // section 6.2.1 Maximum json.Number `json:"maximum,omitempty"` // section 6.2.2 ExclusiveMaximum json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 Minimum json.Number `json:"minimum,omitempty"` // section 6.2.4 ExclusiveMinimum json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 MaxLength *uint64 `json:"maxLength,omitempty"` // section 6.3.1 MinLength *uint64 `json:"minLength,omitempty"` // section 6.3.2 Pattern string `json:"pattern,omitempty"` // section 6.3.3 MaxItems *uint64 `json:"maxItems,omitempty"` // section 6.4.1 MinItems *uint64 `json:"minItems,omitempty"` // section 6.4.2 UniqueItems bool `json:"uniqueItems,omitempty"` // section 6.4.3 MaxContains *uint64 `json:"maxContains,omitempty"` // section 6.4.4 MinContains *uint64 `json:"minContains,omitempty"` // section 6.4.5 MaxProperties *uint64 `json:"maxProperties,omitempty"` // section 6.5.1 MinProperties *uint64 `json:"minProperties,omitempty"` // section 6.5.2 Required []string `json:"required,omitempty"` // section 6.5.3 DependentRequired map[string][]string `json:"dependentRequired,omitempty"` // section 6.5.4 // RFC draft-bhutton-json-schema-validation-00, section 7 Format string `json:"format,omitempty"` // RFC draft-bhutton-json-schema-validation-00, section 8 ContentEncoding string `json:"contentEncoding,omitempty"` // section 8.3 ContentMediaType string `json:"contentMediaType,omitempty"` // section 8.4 ContentSchema *Schema `json:"contentSchema,omitempty"` // section 8.5 // RFC draft-bhutton-json-schema-validation-00, section 9 Title string `json:"title,omitempty"` // section 9.1 Description string `json:"description,omitempty"` // section 9.1 Default any `json:"default,omitempty"` // section 9.2 Deprecated bool `json:"deprecated,omitempty"` // section 9.3 ReadOnly bool `json:"readOnly,omitempty"` // section 9.4 WriteOnly bool `json:"writeOnly,omitempty"` // section 9.4 Examples []any `json:"examples,omitempty"` // section 9.5 Extras map[string]any `json:"-"` // Special boolean representation of the Schema - section 4.3.2 boolean *bool } var ( // TrueSchema defines a schema with a true value TrueSchema = &Schema{boolean: &[]bool{true}[0]} // FalseSchema defines a schema with a false value FalseSchema = &Schema{boolean: &[]bool{false}[0]} ) // Definitions hold schema definitions. // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 // RFC draft-wright-json-schema-validation-00, section 5.26 type Definitions map[string]*Schema jsonschema-0.13.0/utils.go000066400000000000000000000014061473503324600154240ustar00rootroot00000000000000package jsonschema import ( "regexp" "strings" orderedmap "github.com/wk8/go-ordered-map/v2" ) var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") // ToSnakeCase converts the provided string into snake case using dashes. // This is useful for Schema IDs and definitions to be coherent with // common JSON Schema examples. func ToSnakeCase(str string) string { snake := matchFirstCap.ReplaceAllString(str, "${1}-${2}") snake = matchAllCap.ReplaceAllString(snake, "${1}-${2}") return strings.ToLower(snake) } // NewProperties is a helper method to instantiate a new properties ordered // map. func NewProperties() *orderedmap.OrderedMap[string, *Schema] { return orderedmap.New[string, *Schema]() }