Skip to content

Commit

Permalink
Merge pull request #23 from invopop/anonymous-embed
Browse files Browse the repository at this point in the history
Refactor type handling for maps and types, removing YAML
  • Loading branch information
samlown committed Jul 5, 2022
2 parents 297bb07 + e7dcddf commit d9664d9
Show file tree
Hide file tree
Showing 27 changed files with 384 additions and 360 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Expand Up @@ -18,4 +18,4 @@ jobs:
- name: Lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.42
version: v1.45
1 change: 1 addition & 0 deletions .golangci.yml
Expand Up @@ -51,6 +51,7 @@ linters:
- structcheck
- stylecheck
- exhaustive
- varnamelen

linters-settings:
govet:
Expand Down
75 changes: 13 additions & 62 deletions README.md
Expand Up @@ -18,6 +18,11 @@ This repository is a fork of the original [jsonschema](https://github.com/alecth
- 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 branches, and reach out if you think something can be improved.

## Example

Expand Down Expand Up @@ -111,6 +116,12 @@ jsonschema.Reflect(&TestUser{})
}
```

## 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`
Expand Down Expand Up @@ -175,65 +186,6 @@ will output:
}
```

### PreferYAMLSchema

JSON schemas can also be used to validate YAML, however YAML frequently uses
different identifiers to JSON indicated by the `yaml:` tag. The `Reflector` will
by default prefer `json:` tags over `yaml:` tags (and only use the latter if the
former are not present). This behavior can be changed via the `PreferYAMLSchema`
flag, that will switch this behavior: `yaml:` tags will be preferred over
`json:` tags.

With `PreferYAMLSchema: true`, the following struct:

```go
type Person struct {
FirstName string `json:"FirstName" yaml:"first_name"`
}
```

would result in this schema:

```json
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/TestYamlAndJson",
"$defs": {
"Person": {
"required": ["first_name"],
"properties": {
"first_name": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
}
}
}
```

whereas without the flag one obtains:

```json
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/TestYamlAndJson",
"$defs": {
"Person": {
"required": ["FirstName"],
"properties": {
"first_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.
Expand Down Expand Up @@ -296,7 +248,7 @@ In some situations, the keys actually used to write files are different from Go

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 aforementionned transformations, without having to specify `json:"..."` on every struct field.
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

Expand Down Expand Up @@ -343,8 +295,7 @@ Will yield
}
```

As you can see, if a field name has a `json:""` or `yaml:""` tag set, the `key` argument to `KeyNamer` will have the value of that tag (if a field name has both, the value of `key` will respect [`PreferYAMLSchema`](#preferyamlschema)).

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

Expand Down
6 changes: 6 additions & 0 deletions examples/nested/nested.go
Expand Up @@ -6,6 +6,12 @@ type Pet struct {
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.
Expand Down
7 changes: 5 additions & 2 deletions examples/user.go
Expand Up @@ -15,8 +15,11 @@ type User struct {
Tags map[string]interface{} `json:"tags,omitempty"`

// An array of pets the user cares for.
Pets []*nested.Pet `json:"pets"`
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=Pants"`
Plants []*nested.Plant `json:"plants" jsonschema:"title=Plants"`
}
5 changes: 4 additions & 1 deletion examples_test.go
Expand Up @@ -21,7 +21,10 @@ type SampleUser struct {

func ExampleReflect() {
s := jsonschema.Reflect(&SampleUser{})
data, _ := json.MarshalIndent(s, "", " ")
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err.Error())
}
fmt.Println(string(data))
// Output:
// {
Expand Down
18 changes: 12 additions & 6 deletions fixtures/allow_additional_props.json
Expand Up @@ -3,6 +3,10 @@
"$id": "https://github.com/invopop/jsonschema/test-user",
"$ref": "#/$defs/TestUser",
"$defs": {
"Bytes": {
"type": "string",
"contentEncoding": "base64"
},
"GrandfatherType": {
"properties": {
"family_name": {
Expand All @@ -14,14 +18,14 @@
"family_name"
]
},
"MapType": {
"type": "object"
},
"TestUser": {
"properties": {
"some_base_property": {
"type": "integer"
},
"some_base_property_yaml": {
"type": "integer"
},
"grand": {
"$ref": "#/$defs/GrandfatherType"
},
Expand All @@ -31,6 +35,9 @@
"PublicNonExported": {
"type": "integer"
},
"MapType": {
"$ref": "#/$defs/MapType"
},
"id": {
"type": "integer"
},
Expand Down Expand Up @@ -98,8 +105,7 @@
"contentEncoding": "base64"
},
"photo2": {
"type": "string",
"contentEncoding": "base64"
"$ref": "#/$defs/Bytes"
},
"feeling": {
"oneOf": [
Expand Down Expand Up @@ -197,10 +203,10 @@
"type": "object",
"required": [
"some_base_property",
"some_base_property_yaml",
"grand",
"SomeUntaggedBaseProperty",
"PublicNonExported",
"MapType",
"id",
"name",
"password",
Expand Down
13 changes: 13 additions & 0 deletions fixtures/array_type.json
@@ -0,0 +1,13 @@
{
"$schema": "http://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"
}
}
}
16 changes: 8 additions & 8 deletions fixtures/commas_in_pattern.json
@@ -1,22 +1,22 @@
{
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/pattern-test",
"$ref": "#/$defs/PatternTest",
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$defs": {
"PatternTest": {
"required": [
"with_pattern"
],
"properties": {
"with_pattern": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "[0-9]{1,4}",
"type": "string"
"pattern": "[0-9]{1,4}"
}
},
"additionalProperties": false,
"type": "object"
"type": "object",
"required": [
"with_pattern"
]
}
}
}
18 changes: 12 additions & 6 deletions fixtures/defaults_expanded_toplevel.json
Expand Up @@ -2,6 +2,10 @@
"$schema": "http://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": {
Expand All @@ -13,15 +17,15 @@
"required": [
"family_name"
]
},
"MapType": {
"type": "object"
}
},
"properties": {
"some_base_property": {
"type": "integer"
},
"some_base_property_yaml": {
"type": "integer"
},
"grand": {
"$ref": "#/$defs/GrandfatherType"
},
Expand All @@ -31,6 +35,9 @@
"PublicNonExported": {
"type": "integer"
},
"MapType": {
"$ref": "#/$defs/MapType"
},
"id": {
"type": "integer"
},
Expand Down Expand Up @@ -98,8 +105,7 @@
"contentEncoding": "base64"
},
"photo2": {
"type": "string",
"contentEncoding": "base64"
"$ref": "#/$defs/Bytes"
},
"feeling": {
"oneOf": [
Expand Down Expand Up @@ -198,10 +204,10 @@
"type": "object",
"required": [
"some_base_property",
"some_base_property_yaml",
"grand",
"SomeUntaggedBaseProperty",
"PublicNonExported",
"MapType",
"id",
"name",
"password",
Expand Down
28 changes: 23 additions & 5 deletions fixtures/go_comments.json
Expand Up @@ -3,6 +3,15 @@
"$id": "https://github.com/invopop/jsonschema/examples/user",
"$ref": "#/$defs/User",
"$defs": {
"NamedPets": {
"patternProperties": {
".*": {
"$ref": "#/$defs/Pet"
}
},
"type": "object",
"description": "NamedPets is a map of animal names to pets."
},
"Pet": {
"properties": {
"name": {
Expand All @@ -18,6 +27,13 @@
],
"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": {
Expand Down Expand Up @@ -62,18 +78,19 @@
"type": "object"
},
"pets": {
"items": {
"$ref": "#/$defs/Pet"
},
"type": "array",
"$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": "Pants",
"title": "Plants",
"description": "Set of plants that the user likes"
}
},
Expand All @@ -83,6 +100,7 @@
"id",
"name",
"pets",
"named_pets",
"plants"
],
"description": "User is used as a base to provide tests for comments."
Expand Down

0 comments on commit d9664d9

Please sign in to comment.