Code Monkey home page Code Monkey logo

go-jsonschema's Introduction

go-jsonschema is a tool to generate Go data types from JSON Schema definitions.

This tool generates Go data types and structs that corresponds to definitions in the schema, along with unmarshalling code that validates the input JSON according to the schema's validation rules.

Badges

GitHub release (latest SemVer) GitHub Workflow Status (event) License GitHub go.mod Go version GitHub code size in bytes GitHub repo file count (file type) GitHub all releases GitHub commit activity Conventional Commits Codecov Code Climate maintainability Go Report Card

Installing

  • Download: Get a release here.

  • Install from source: To install with Go 1.16+:

go get github.com/atombender/go-jsonschema/...
go install github.com/atombender/go-jsonschema@latest
  • Install with Brew: To install with Homebrew:
brew tap omissis/go-jsonschema
brew install go-jsonschema

Contributing

This project makes use of go workspaces in order to ease testing of the generated code during development while keeping the codebase as tidy and maintainable as possible. It's an unusual choice, but it allows to not only test the code-generation logic, but also the generated code itself.

Usage

At its most basic:

go-jsonschema -p main schema.json

This will write a Go source file to standard output, declared under the package main.

You can generate code for multiple schemas in one run, optionally writing to different files inside different packages:

$ go-jsonschema \
  --schema-package=https://example.com/schema1=github.com/myuser/myproject \
   --schema-output=https://example.com/schema1=schema1.go \
  --schema-package=https://example.com/schema2=github.com/myuser/myproject/stuff \
   --schema-output=https://example.com/schema2=stuff/schema2.go \
  schema1.json schema2.json

This will create schema1.go (declared as package myproject) and stuff/schema2.go (declared as package stuff). If schema1.json refers to schema2.json or viceversa, the two Go files will import the other depended-on package. Note the flag format:

--schema-package=https://example.com/schema1=github.com/myuser/myproject \
                 ^                           ^
                 |                           |
                 schema $id                  full import URL

Special types

In a few cases, special types are used to help with serializing/deserializing data frrom JSON. Namely a custom types is provided for the following semantic types:

  • SerializableDate
  • SerializableTime

These types are needed because there is no native type provided by Go which properly handles them.

Status

While not finished, go-jsonschema can be used today. Aside from some minor features, only specific validations remain to be fully implemented.

Validation

  • Core (RFC draft)
    • Data model (§4.2.1)
      • null
      • boolean
      • object
      • array
      • number
        • Option to use json.Number
      • string
    • Location identifiers (§8.2.3)
      • References against top-level names: #/$defs/someName
      • References against nested names: #/$defs/someName/$defs/someOtherName
      • References against top-level names in external files: myschema.json#/$defs/someName
      • References against nested names: myschema.json#/$defs/someName/$defs/someOtherName
    • Comments (§9)
  • Validation (RFC draft)
    • Schema annotations (§10)
      • description
      • default (only for struct fields)
      • readOnly
      • writeOnly
      • title (N/A)
      • examples (N/A)
    • General validation (§6.1)
      • enum
      • type (single)
      • type (multiple; note: partial support, limited validation)
      • const
    • Numeric validation (§6.2)
      • multipleOf
      • maximum
      • exclusiveMaximum
      • minimum
      • exclusiveMinimum
    • String validation (§6.3)
      • maxLength
      • minLength
      • pattern
    • Array validation (§6.4)
      • items
      • maxItems
      • minItems
      • uniqueItems
      • additionalItems
      • contains
    • Object validation (§6.5)
      • required
      • properties
      • patternProperties
      • dependencies
      • propertyNames
      • maxProperties
      • minProperties
    • Conditional subschemas (§6.6)
      • if
      • then
      • else
    • Boolean subschemas (§6.7)
      • allOf
      • anyOf
      • oneOf
      • not
    • Semantic formats (§7.3)
      • Dates and times
      • Email addresses
      • Hostnames
      • IP addresses
      • Resource identifiers
      • URI-template
      • JSON pointers
      • Regex

License

MIT license. See LICENSE file.

go-jsonschema's People

Contributors

al-pragliola avatar albertobarba avatar andrew-farries avatar atombender avatar daa avatar deep-creek avatar durandj avatar felixjung avatar gonzaloserrano avatar haya14busa avatar henkoglobin avatar icedream avatar jagregory avatar johanneswuerbach avatar ksysoev avatar kwuerl avatar louis77 avatar luigibarbato avatar nick-jones avatar omissis avatar pantafive avatar renovate[bot] avatar robquistnl avatar shanduur avatar teq0 avatar tjungblu avatar vaihtovirta avatar xwjdsh avatar zapling avatar zrma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-jsonschema's Issues

Feature Request - Generate struct name from title

Hey, I'm currently looking at using this for automating struct generation for some projects I'm working on, and for automation's sake I'd like to be generate the commands to generate these programatically. In this example, schema.json in foo/bot/v1alpha1 references the schema in foo/shaz/alpha1

Ideally I'd like to have something check the files and their parent directory to determine their package/struct names, however in it's current form this will take the schema in foo/shaz/alpha1/schema.json and name it SchemaJson1 since foo/bot/v1alpha1/schema.json will be named SchemaJson, this is because go-jsonschema generates struct names based off of the file names.

.
├── README.md
└── foo
    ├── shaz
    │   └── v1alpha1
    │       └── schema.json
    ├── bot
    │   └── v1alpha1
    │       └── schema.json

If possible, can we add a flag that creates the struct names from their title?
For example I have a schema like:

{
    "$id": "https://github.com/eahrend/foo/shaz",
    "description": "A representation of shaz",
    "title":"shaz",
    "$schema": "http://json-schema.org/draft-06/schema#",
    "type":"object",
    "properties":{
       "key":{
          "type":"string"
       }
}

I'd like the generator to be able to turn this struct into a struct named shaz:

type Shaz struct {
  Key string `json:"key"`
}

Object Properties do not generate valid Unmarshal code

The following schema fails to produce code that compiles due to foo property being typed as map[string]string but unmarshalled as map[string]interface{}

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
        "foo": {
            "type": "object",
            "additionalProperties": {
                "type": "string"
            },
            "default": {}
        },
        "bar": {
            "type": "string",
            "default": "example"
        },
        "baz": {
            "type": "string",
            "default": "example"
        }
    },
    "additionalProperties": false
}

Generate time.Time from string/date-time

Would it be possible to generate a Go time.Time type from schema string/date-time? Example:

    "start_date": {
      "description": "The actual start date",
      "type": "string",
      "format": "date-time"
    }

currently creates

StartDate string `json:"start_date"`

but it should be

StartDate time.Time `json:"start_date"`

For my use case I do not require validation, so generating the correct type (time.Time) would suffice.

Incorrect root type name generation

When generating code from a YAML schema definition, the automatically generated struct name for the root type has a trailing Yaml suffix because the file extension isn't trimmed correctly from the filename.

Example

Input

# mySchema.yaml

"$schema": "http://json-schema.org/draft-04/schema#"
"$id": "https://example.com/yaml_root_type_name"

type: object
properties:
  foo:
    type: string

Expected Output

// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.

package test

type MySchema struct {
	Foo *string `json:"foo,omitempty" yaml:"foo,omitempty"`
}

Actual Output

// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.

package test

type MySchemaYaml struct {
	Foo *string `json:"foo,omitempty" yaml:"foo,omitempty"`
}

Tests are failing

When running the tests some of them fail. It seems like new expected output needs to be generated.

How to specify a root directory for `$ref` resolution?

Prerequisite

I have a bundle of json schema files on local file system as below:

/path/to/base/dir/
└── schema/
    └── v2/
        ├── named_api_resource.json
        └── pokemon/
            └── $id/
                └── index.json

In schema/v2/pokemon/$id/index.json , There is a $ref to /schema/v2/pokemon/$id/index.json:

{
    "$schema": "http://json-schema.org/schema#",
    "properties": {
        "abilities": {
            "items": {
                "properties": {
                    "ability": {
                        "$ref": "/schema/v2/named_api_resource.json"
                    }
                },
                "required": [
                    "ability"
                ],
                "type": "object"
            },
            "type": "array"
        }
    },
    "required": [
        "abilities"
    ],
    "type": "object"
}

What did you expect to happen?

I want /schema/v2/named_api_resource.json can be correctly resolved to /path/to/base/dir/schema/v2/named_api_resource.json .

These schemas are downloaded from internet and I prefer not to modify these schemas.

What happened?

I tried to generate these schemas using command below:

go-jsonschema -p schema ./schema/v2/pokemon/\$id/index.json ./schema/v2/named_api_resource.json

And of course it failed, with follow message:

go-jsonschema: Failed: could not generate type for field "abilities": could not generate type for field "ability": could not follow $ref "/schema/v2/named_api_resource.json" to file "/schema/v2/named_api_resource.json": error resolving symlinks in : lstat /schema: no such file or directory

Possible Reason

And I think the reason is /schema/v2/named_api_resource.json was resolved as the absolute path to my file system:

func (g *Generator) loadSchemaFromFile(fileName, parentFileName string) (*schemas.Schema, error) {
if !filepath.IsAbs(fileName) {
fileName = filepath.Join(filepath.Dir(parentFileName), fileName)
}
exts := append([]string{""}, g.config.ResolveExtensions...)
for i, ext := range exts {
qualified := fileName + ext
// Poor man's resolving loop.
if i < len(exts)-1 && !fileExists(qualified) {
continue
}
var err error
qualified, err = filepath.EvalSymlinks(qualified)
if err != nil {
return nil, fmt.Errorf("error resolving symlinks in %s: %w", qualified, err)
}

Question

Is there any way to specify a root directory for reference resolution? If not, may I contribute to this feature?

Master tests broken since "YAML commits"

From 0c601dda828c010901ca01e9cfcb8cf7649f90c3 commit, which introduces support for generating the code from a YAML file (at least that's what I understood), the tests are broken since the types now get generated with a trailing -Json suffix.

I tried to update the tests at first in order to unblock open PRs, but actually debugging the issue made me realize that the types should not contain the input mechanism as part of their name.
Here (I think) is the place where the bug was introduced.

After this debugging session, my conclusion is that the YAML feature is not very well documented, justified, and decoupled from JSON implementation (probably because the YAML addition was not planned since the beginning).

Handling of `required` properties in objects

According to §6.5.3 of the JSON Schema Validation spec, required properties need merely be present in the input JSON, they are not required to be non-null.

As such, the validation generated by go-jsonschema is too strict, as it checks for presence (which is correct) but also validates that the value is not nil.

For example, with the following schema:

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "RequiredObjectPropertiesTest",
    "properties": {
        "requiredNillableProp": {
            "type": [
                "string",
                "null"
            ]
        }
    },
    "required": [
        "requiredNillableProp"
    ]
}

This input is valid:

{
  "requiredNillableProp": null	
}

But an empty JSON object {} is not.

As such, properties marked as required should generate without omitempty and should be checked for presence, but not for non-nil.

`maxLength` is not working as expected according to the jsonschema docs

maxLength is not working as expected according to the jsonschema docs

{
  "type": "string",
  "minLength": 2,
  "maxLength": 3
}

"A" 🔴
"AB" 🟢
"ABC" 🟢
"ABCD" 🔴

Example:

json file:

"maxLength": 20

generated:

// UnmarshalJSON implements json.Unmarshaler.
func (j *ModemType) UnmarshalJSON(b []byte) error {
	var raw map[string]interface{}
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	type Plain Type
	var plain Plain
	if err := json.Unmarshal(b, &plain); err != nil {
		return err
	}

        // IT SHOULD BE "len(*plain.field) > 20" instead
	if plain.field != nil && len(*plain.field) >= 20 {
		return fmt.Errorf("field %s length: must be <= %d", "field", 20)
	}
	*j = Type(plain)
	return nil
}

JSON schema docs: https://json-schema.org/understanding-json-schema/reference/string#length

Special characters in names get removed instead of transformed leading to name collisions

I recognize this is an edge case.

If I have something like

schema.json:

...
"type": string
"enum": [
  "GPL-3.0",
  "GPL-3.0+",
]

This will lead to golang code like:

SchemaJsonGPL30 = "GPL-3.0"
SchemaJsonGPL30 = "GPL-30+"

This leads to name collisions. Is there a way to get it to convert special characters into English equivalents? For example:

SchemaJsonGPLDash3Dot0
SchemaJsonGPLDash3Dot0Plus

Allow defaulting based on the ID instead of the filename

While the path of the ID isn't always good, it would be more semantic than file name conventions in my case. Ex I would like "id": "https://www.getenvoy.io/envoyBuilds" to result in EnvoyBuilds as the top-level type instead of having to override via a mapping.

should integer be big.Int ?

jsonschema doesnt define a maximum length for the integer, so i do think it should be translated to big.Int

would PR, but mosts of the tests are failing.

Support root array types in UnmarshalJSON

Currently, the UnmarshalJSON generated for schemas that include root arrays doesn't unmarshal the array, rather the elements of it. This limits is usefulness as we end up needing to chop up the json or not use the UnmarshalJSON tool. I'm not sure the best approach to attack this, so hopefully adding test data that shows this will help.

ex

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id": "https://example.com/rootArrayOfObject",
  "type": "array",
  "items": {
    "$ref": "#/definitions/MyObject"
  },
  "definitions": {
    "MyObject": {
      "type": "object",
      "required": ["myString"],
      "properties": {
        "myString": {
          "type": "string"
        }
      }
    }
  }
}

Generates this

func (j *MyObject) UnmarshalJSON(b []byte) error {

instead of an array of that.

master...codefromthecrypt:rootArrayOfObject

special case objects with only string values

When unmarshalling, I think we can special-case when the json represented only includes string fields.

Instead of

func (j *Download) UnmarshalJSON(b []byte) error {
	var raw map[string]interface{}
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	if v, ok := raw["architecture"]; !ok || v == nil {

this

func (j *Download) UnmarshalJSON(b []byte) error {
	var raw map[string]string
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	if v, ok := raw["architecture"]; !ok || v == "" {

Doesn't generate Go structs for http://hl7.org/fhir/json-schema/HumanName

When running go-jsonschema on the payload below, no struct is being generated

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id": "http://hl7.org/fhir/json-schema/HumanName",
  "$ref": "#/definitions/HumanName",
  "description": "see http://hl7.org/fhir/json.html#schema for information about the FHIR Json Schemas",
  "definitions": {
    "HumanName": {
      "allOf": [
        {
          "$ref": "Element#/definitions/Element"
        },
        {
          "description": "A human\u0027s name with the ability to identify parts and usage.",
          "properties": {
            "use": {
              "description": "Identifies the purpose for this name.",
              "enum": [
                "usual",
                "official",
                "temp",
                "nickname",
                "anonymous",
                "old",
                "maiden"
              ],
              "type": "string"
            },
            "_use": {
              "description": "Extensions for use",
              "$ref": "Element.schema.json#/definitions/Element"
            },
            "text": {
              "description": "A full text representation of the name.",
              "type": "string"
            },
            "_text": {
              "description": "Extensions for text",
              "$ref": "Element.schema.json#/definitions/Element"
            },
            "family": {
              "description": "The part of a name that links to the genealogy. In some cultures (e.g. Eritrea) the family name of a son is the first name of his father.",
              "type": "string"
            },
            "_family": {
              "description": "Extensions for family",
              "$ref": "Element.schema.json#/definitions/Element"
            },
            "given": {
              "description": "Given name.",
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "_given": {
              "description": "Extensions for given",
              "type": "array",
              "items": {
                "$ref": "Element.schema.json#/definitions/Element"
              }
            },
            "prefix": {
              "description": "Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the start of the name.",
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "_prefix": {
              "description": "Extensions for prefix",
              "type": "array",
              "items": {
                "$ref": "Element.schema.json#/definitions/Element"
              }
            },
            "suffix": {
              "description": "Part of the name that is acquired as a title due to academic, legal, employment or nobility status, etc. and that appears at the end of the name.",
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "_suffix": {
              "description": "Extensions for suffix",
              "type": "array",
              "items": {
                "$ref": "Element.schema.json#/definitions/Element"
              }
            },
            "period": {
              "description": "Indicates the period of time when this name was valid for the named person.",
              "$ref": "Period.schema.json#/definitions/Period"
            }
          }
        }
      ]
    }
  }
}

additionalProperties parsing is broken in v0.9.0

input:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "additionalProperties": true,
  "properties": {
    "name": {
      "type": "string"
    }
  }
}

reproduce the problem:

❯ go install github.com/atombender/go-jsonschema/cmd/gojsonschema@latest
❯ gojsonschema -p main with_additional_properties.json

gojsonschema: Failed: error parsing from file with_additional_properties.json: json: cannot unmarshal bool into Go struct field unmarshalerSchema.additionalProperties of type schemas.Type

downgrade to v0.8.0 fix the problem:

❯ go install github.com/atombender/go-jsonschema/[email protected]
❯ gojsonschema -p main with_additional_properties.json

// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.

package main

type WithAdditionalProperties struct {
        // Name corresponds to the JSON schema field "name".
        Name *string `json:"name,omitempty"`
}

Wishlist: additionalProperties restrictions on object values

It seems additionalProperties are ignored.

	"headers": {
	    "type": "object",
	    "properties": {},
	    "additionalProperties": {
		"type": "string"
	    }
	}

translates to

type FooHeaders map[string]interface{}

where I would really like to have a map[string]string for that use case.

Similarly, I would want a map[string][]string elsewhere.

Use case: HTTP etc headers. I know for a fact the values will only ever be strings.

Feature request: support configuring generated struct tags

Currently the generated code always sets a struct tag for json, yaml, and mapstructure. See:

if isRequired {
structField.Tags = fmt.Sprintf(`json:"%s" yaml:"%s" mapstructure:"%s"`, name, name, name)
} else {
structField.Tags = fmt.Sprintf(`json:"%s,omitempty" yaml:"%s,omitempty" mapstructure:"%s,omitempty"`,
name, name, name)

It would be great if it was possible to pass in a configuration option to specify which of these to set.

local file ref failure

repro:

go install github.com/atombender/go-jsonschema/cmd/gojsonschema@latest

(btw consider fixing it so the github.com/omissis/go-jsonschema path works)

in ids.json

{
  "id": {
    "enum": [
       "a",
       "b"
    ]
  }
}

in schema.json

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Bug",
  "type": "array",
  "uniqueItems": true,
  "items": {
    "type": "object",
    "properties": {
      "idref": {
        "$ref": "file://./ids.json#/id"
      }
    }
  }
}

then

$ gojsonschema -p main schema.json 
go-jsonschema: Failed: could not generate type for field "idref": unsupported $ref format; must point to definition within file: "file://./ids.json#/id"

Why not gengerate int enum const

Say we have this schema

{
    "title": "Example JSON schema",
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "additionalProperties": false,
    "required": [
        "values"
    ],
    "properties": {
        "values": {
            "description": "example enum values",
            "enum": [
                1,
                2,
                3
            ]
        }
    }
}

The following generated codes only contains type with no int const, say, const SchemaValuesA1 SchemaValues = 1,

Generated code
// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.

package generated

import "fmt"
import "reflect"
import "encoding/json"

type SchemaValues int

var enumValues_SchemaValues = []interface{}{
        1,
        2,
        3,
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *SchemaValues) UnmarshalJSON(b []byte) error {
        var v int
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_SchemaValues {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_SchemaValues, v)
        }
        *j = SchemaValues(v)
        return nil
}

type Schema struct {
        // example enum values
        Values SchemaValues `json:"values"`
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *Schema) UnmarshalJSON(b []byte) error {
        var raw map[string]interface{}
        if err := json.Unmarshal(b, &raw); err != nil {
                return err
        }
        if v, ok := raw["values"]; !ok || v == nil {
                return fmt.Errorf("field values: required")
        }
        type Plain Schema
        var plain Plain
        if err := json.Unmarshal(b, &plain); err != nil {
                return err
        }
        *j = Schema(plain)
        return nil
}

If I replace int values by string values, like this

{
    "title": "Example JSON schema",
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "additionalProperties": false,
    "required": [
        "values"
    ],
    "properties": {
        "values": {
            "description": "example enum values",
            "type": "integer",
            "enum": [
                "1",      <<--- replace with string enum values
                "2",
                "3"
            ]
        }
    }
}

then I got generated code with consts, like const SchemaValuesA1 SchemaValues = "1" which can be accessed outside the package so that it's more convinient than convert int to SchemaValues type.

Generated code
// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.

package generated

import "fmt"
import "reflect"
import "encoding/json"

type Schema struct {
        // example enum values
        Values SchemaValues `json:"values"`
}

var enumValues_SchemaValues = []interface{}{
        "1",
        "2",
        "3",
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *SchemaValues) UnmarshalJSON(b []byte) error {
        var v string
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_SchemaValues {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_SchemaValues, v)
        }
        *j = SchemaValues(v)
        return nil
}

type SchemaValues string

const SchemaValuesA1 SchemaValues = "1"      <<---- THIS IS WAHT I WANT (exported consts)
const SchemaValuesA2 SchemaValues = "2"
const SchemaValuesA3 SchemaValues = "3"

// UnmarshalJSON implements json.Unmarshaler.
func (j *Schema) UnmarshalJSON(b []byte) error {
        var raw map[string]interface{}
        if err := json.Unmarshal(b, &raw); err != nil {
                return err
        }
        if v, ok := raw["values"]; !ok || v == nil {
                return fmt.Errorf("field values: required")
        }
        type Plain Schema
        var plain Plain
        if err := json.Unmarshal(b, &plain); err != nil {
                return err
        }
        *j = Schema(plain)
        return nil
}

null type missing validation

{
  "definitions": {},
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "whatever",
  "type": "object",
  "title": "The Root Schema",
  "properties": {
    "test": {
      "type": "null"
    }
  }
}

For the above json schema, it generates the follwing go code:

type Test struct {
        // Test corresponds to the JSON schema field "test".
        Test interface{} `json:"test,omitempty"`
}

But according to the JSON schema doc,

When a schema specifies a type of null, it has only one acceptable value: null.

Should we add validation to ensure the field is nil in unmarshaling? like this:

type Test struct {
        // Test corresponds to the JSON schema field "test".
        Test interface{} `json:"test,omitempty"`
}

func (j *Test) UnmarshalJSON(b []byte) error {
	var raw map[string]interface{}
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	if v, ok := raw["test"]; ok && v != nil {
		return fmt.Errorf("field test: null")
	}
	type Plain Test
	var plain Plain
	if err := json.Unmarshal(b, &plain); err != nil {
		return err
	}
	*j = Test(plain)
	return nil
}

If you agree, I'd like to draft this PR.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

asdf
.tool-versions
  • adr-tools 3.0.0
  • golang 1.22.2
  • golangci-lint 1.58.0
  • hadolint 2.12.0
  • shellcheck 0.10.0
  • shfmt 3.8.0
dockerfile
Dockerfile
  • golang 1.22.2-alpine3.18
github-actions
.github/workflows/development.yaml
  • actions/checkout v4@0ad4b8fadaa221de15dcec353f45205ec38ea70b
  • actions/setup-go v5
  • codecov/codecov-action v4
  • goreleaser/goreleaser-action v5
  • ubuntu 22.04
.github/workflows/prerelease.yaml
  • actions/checkout v4@0ad4b8fadaa221de15dcec353f45205ec38ea70b
  • actions/setup-go v5
  • goreleaser/goreleaser-action v5
  • ubuntu 22.04
.github/workflows/release.yaml
  • actions/checkout v4@0ad4b8fadaa221de15dcec353f45205ec38ea70b
  • actions/setup-go v5
  • docker/login-action v3
  • goreleaser/goreleaser-action v5
  • ubuntu 22.04
gomod
go.mod
  • go 1.22
  • github.com/goccy/go-yaml v1.11.3
  • github.com/mitchellh/go-wordwrap v1.0.1
  • github.com/pkg/errors v0.9.1
  • github.com/sanity-io/litter v1.5.5
  • github.com/spf13/cobra v1.8.0
  • golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842@9bf2ced13842

  • Check this box to trigger a request for Renovate to run again on this repository

Go install complains on go.mod

Hi,

New version has a problem with installation via running go install.

Run go install github.com/atombender/go-jsonschema/cmd/[email protected]
go: downloading github.com/atombender/go-jsonschema v0.13.0
go: github.com/atombender/go-jsonschema/cmd/[email protected] (in github.com/atombender/[email protected]):
	The go.mod file for the module providing named packages contains one or
	more replace directives. It must not contain directives that would cause
	it to be interpreted differently than if it were the main module.

Multiple refs to enums results in multiple interfaces

Given a schema like:

{
    "$schema": "https://json-schema.org/draft/2019-09/schema",
    "title": "test",
    "definitions": {
        "Foo1": {
            "type": "object",
            "required": [
                "bar"
            ],
            "properties": {
                "bar": {
                    "$ref": "#/definitions/Bar"
                }
            }
        },
        "Foo2": {
            "type": "object",
            "required": [
                "bar"
            ],
            "properties": {
                "bar": {
                    "$ref": "#/definitions/Bar"
                }
            }
        },
        "Bar": {
            "type": "string",
            "enum": [
                "1",
                "2"
            ]
        }
    }
}

I.e. Foo1 and Foo2 both have a bar field of type Bar, which is an enum.

then the output has three different Bar types:

Long code snippet
package main

import "fmt"
import "reflect"
import "encoding/json"

type Bar string

// UnmarshalJSON implements json.Unmarshaler.
func (j *Bar_2) UnmarshalJSON(b []byte) error {
        var v string
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_Bar_2 {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Bar_2, v)
        }
        *j = Bar_2(v)
        return nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *Bar) UnmarshalJSON(b []byte) error {
        var v string
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_Bar {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Bar, v)
        }
        *j = Bar(v)
        return nil
}

const BarA1 Bar = "1"
const BarA2 Bar = "2"

type Bar_1 string

// UnmarshalJSON implements json.Unmarshaler.
func (j *Foo1) UnmarshalJSON(b []byte) error {
        var raw map[string]interface{}
        if err := json.Unmarshal(b, &raw); err != nil {
                return err
        }
        if v, ok := raw["bar"]; !ok || v == nil {
                return fmt.Errorf("field bar: required")
        }
        type Plain Foo1
        var plain Plain
        if err := json.Unmarshal(b, &plain); err != nil {
                return err
        }
        *j = Foo1(plain)
        return nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *Bar_1) UnmarshalJSON(b []byte) error {
        var v string
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_Bar_1 {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Bar_1, v)
        }
        *j = Bar_1(v)
        return nil
}

const Bar_1_A1 Bar_1 = "1"
const Bar_1_A2 Bar_1 = "2"

type Bar_2 string

const Bar_2_A1 Bar_2 = "1"
const Bar_2_A2 Bar_2 = "2"

type Foo1 struct {
        // Bar corresponds to the JSON schema field "bar".
        Bar Bar_1 `json:"bar"`
}

type Foo2 struct {
        // Bar corresponds to the JSON schema field "bar".
        Bar Bar_2 `json:"bar"`
}

var enumValues_Bar = []interface{}{
        "1",
        "2",
}
var enumValues_Bar_1 = []interface{}{
        "1",
        "2",
}
var enumValues_Bar_2 = []interface{}{
        "1",
        "2",
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *Foo2) UnmarshalJSON(b []byte) error {
        var raw map[string]interface{}
        if err := json.Unmarshal(b, &raw); err != nil {
                return err
        }
        if v, ok := raw["bar"]; !ok || v == nil {
                return fmt.Errorf("field bar: required")
        }
        type Plain Foo2
        var plain Plain
        if err := json.Unmarshal(b, &plain); err != nil {
                return err
        }
        *j = Foo2(plain)
        return nil
}

Expected: there is a single Bar type shared between the definitions.

Support multiple types for "type"

Hello!

This project is awesome, but unfortunately seems to support only Draft 5 ( latest available is Draft 7 ). May I open a PR to update the Readme adding this information?

How do you plan to handle Draft releases in this project? Do you plan updating to the new Draft?

Thank you!

mark a new release as latest?

Hi, I'm happy with what you've done and would love to have no questions about including this in a project. Would you mind tagging a new release or setting the last tag as latest? It seems a cheap confidence builder.

Fails to generate schema for github-workflow

Trying to generate schema for github workflow from https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-workflow.json

❯ gojsonschema -v -p main github-workflow.json
go-jsonschema: Loading github-workflow.json
go-jsonschema: Failed: error parsing from file github-workflow.json: error parsing JSON file github-workflow.json: failed to unmarshal JSON: failed to unmarshal schema: failed to unmarshal type: failed to unmarshal type: failed to unmarshal type: failed to unmarshal type: failed to unmarshal type: json: cannot unmarshal array into Go value of type schemas.ObjectAsType

Wishlist: translate ipv4 & ipv6 to Go net.IP

I have a use case where I want to put a net.Conn.LocalAddr (and thus net.IP) into a JSON document. Right now using "type": "string", "format": "ipv4" (or "ipv6", or a oneOf combination of them) results in generated type with Go type string.

Especially for unmarshaling side, having a net.IP there would be nice. Otherwise I'm parsing the IP address again, doing validation after a validation library call.

Explore goplayground's validator

Validation is an important topic for this library, as proven by some of the PRs received by the community, such as #38 and #10.

I would like to explore the possibility to integrate goplayground's validation library as a mean to offer more complete support for json schema validation.

AdditionalProperties are not honored in v0.9.0

I am trying to generate a model from the following schema file.

It works fine with v0.8.0 but fails on v0.9.0

test.json

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "$ref": "#/definitions/TestObject",
  "definitions":
  {
    "TestObject":
    {
      "required":
      [
        "owner",
        "name"
      ],
      "properties":
      {
        "owner":
        {
          "type": "string"
        },
        "name":
        {
          "type": "string"
        },
        "config":
        {
          "patternProperties":
          {
            ".*":
            {
              "additionalProperties": true
            }
          },
          "type": "object"
        }
      },
      "additionalProperties": false,
      "type": "object"
    }
  }
}

Error:

gojsonschema -v -p model ../test.json -o test.go
gojsonschema: Loading ../test.json
gojsonschema: Failed: error parsing from file ../test.json: json: cannot unmarshal bool into Go struct field Type.definitions.properties.patternProperties.additionalProperties of type schemas.Type

Feature request - option to create an interface as well as a struct, and getters and setters for properties

In many situations you want to be able to support multiple versions of schemas at the same time so that your customers don't all need to update their applications and services every time you bump a version. But if you are deserialising into strongly-typed, version-specific structs it's difficult to have efficient code that deals with all the common fields across versions because the different versions are different go types.

Since go "polymorphism" uses interfaces (i.e. methods and not properties) one way to deal with multiple versions would be to define interfaces that consist of getters/setters for all the fields, so that the different versioned structs can at least be accessed through lowest-common-denominator interface definitions e.g. everything implements the v1.0 interface, all versions including and above v1.1 support any new fields added in v1.1 etc.

You could probably do this using reflection at runtime, or maybe something with generics, but it seems like it would be less work all round to do it while generating the code, since you already have the field and struct definitions.

investigation: produce stable output

As of now the tool dumps the go code in a "random sort" fashion, meaning that every time it's run, the order of the symbols (structs, functions, constants, vars, etc) is not predictable.
This is not strictly a bug, but it makes reviewing changes next to impossible, and it would be nice to correct it to allow for easier code inspection between schema versions

Generated Unmarshal for fields with `time.Time`

This JSON schema generates the following types and UnmarshalJSON method:

// CloudEvents Specification JSON Schema
type CloudeventsSchemaJson struct {
	// The event payload.
	Data Datadef `json:"data,omitempty" yaml:"data,omitempty" mapstructure:"data,omitempty"`

	// Base64 encoded event payload. Must adhere to RFC4648.
	DataBase64 DataBase64Def `json:"data_base64,omitempty" yaml:"data_base64,omitempty" mapstructure:"data_base64,omitempty"`

	// Content type of the data value. Must adhere to RFC 2046 format.
	Datacontenttype Datacontenttypedef `json:"datacontenttype,omitempty" yaml:"datacontenttype,omitempty" mapstructure:"datacontenttype,omitempty"`

	// Identifies the schema that data adheres to.
	Dataschema Dataschemadef `json:"dataschema,omitempty" yaml:"dataschema,omitempty" mapstructure:"dataschema,omitempty"`

	// Identifies the event.
	Id Iddef `json:"id" yaml:"id" mapstructure:"id"`

	// Identifies the context in which an event happened.
	Source Sourcedef `json:"source" yaml:"source" mapstructure:"source"`

	// The version of the CloudEvents specification which the event uses.
	Specversion Specversiondef `json:"specversion" yaml:"specversion" mapstructure:"specversion"`

	// Describes the subject of the event in the context of the event producer
	// (identified by source).
	Subject Subjectdef `json:"subject,omitempty" yaml:"subject,omitempty" mapstructure:"subject,omitempty"`

	// Timestamp of when the occurrence happened. Must adhere to RFC 3339.
	Time Timedef `json:"time,omitempty" yaml:"time,omitempty" mapstructure:"time,omitempty"`

	// Describes the type of event related to the originating occurrence.
	Type Typedef `json:"type" yaml:"type" mapstructure:"type"`
}

type DataBase64Def *string

type Datacontenttypedef *string

type Datadef interface{}

type Dataschemadef *string

type Iddef string

type Sourcedef string

type Specversiondef string

type Subjectdef *string

type Timedef *time.Time

type Typedef string

// UnmarshalJSON implements json.Unmarshaler.
func (j *CloudeventsSchemaJson) UnmarshalJSON(b []byte) error {
	var raw map[string]interface{}
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	if v, ok := raw["id"]; !ok || v == nil {
		return fmt.Errorf("field id in CloudeventsSchemaJson: required")
	}
	if v, ok := raw["source"]; !ok || v == nil {
		return fmt.Errorf("field source in CloudeventsSchemaJson: required")
	}
	if v, ok := raw["specversion"]; !ok || v == nil {
		return fmt.Errorf("field specversion in CloudeventsSchemaJson: required")
	}
	if v, ok := raw["type"]; !ok || v == nil {
		return fmt.Errorf("field type in CloudeventsSchemaJson: required")
	}
	type Plain CloudeventsSchemaJson
	var plain Plain
	if err := json.Unmarshal(b, &plain); err != nil {
		return err
	}
	*j = CloudeventsSchemaJson(plain)
	return nil
}

The json.Unmarshal(data, &cloudevent) for it results in an error:

	err := json.Unmarshal([]byte(`{
		"data":{"exampleField":"a potato flew around", "enumField": "one potato"},
		"source":"mysource",
		"type":"public.domain.resource.action",
		"id":"7a929ab4-cd97-4208-8e49-98d9f4d88881",
		"time":"2023-08-02T17:53:08.614Z",
		"specversion":"1.0"
	}`), &cloudevent)
Failed to unmarshal JSON: &json.UnmarshalTypeError{Value:"string", Type:(*reflect.rtype)(0x11b6de0), Offset:88, Struct:"Plain", Field:"time"}

Is there a way for the generated UnmarshalJSON to account for Unmarshalling the Time field?

We've resorted to implementing our own UnmarshalJSON and passing --only-models in the generate command for the time being.

Advise on "open enums"

We have a part of our json schema which prescribes values based on what's supported now. ex.

        "os": {
          "title": "OS",
          "description": "OS is the supported operating system (runtime.GOOS)",
          "type": "string",
          "enum": [
            "darwin",
            "linux",
            "windows"
          ]
        },

However, if we added another value later, we would want old code to not fail. Right now enums are validated by default.. We still want to know what the constants should be... Any advise?

minItems wrong implementation

Hi everyone, first of all, thank you for your work.
I'm using your tool and up to yesterday, I never had to do with the minItems property in an array.
Yesterday I had, I had some problems you can follow here:
cosmos/chain-registry#2352

After some reasoning with the reviewer on that repo, I better investigated the issue. I'll report here the results as I did on the other repo (cosmos/chain-registry#2353).

Since it was unclear to me from the documentation, I tried a validator online and I understood that the minItems property only requires at least an "image" if the "image" field is defined.

The code generated through go-jsonschema instead, makes the field required. So If there is no image, (minItems = 1) the object is not validated. For this reason, it seems to be conceptually wrong.

What do you think?
Thank you for your time!

Folded blocks in YAML cause extra line breaks

When generating code from a YAML schema definition that uses folded blocks for descriptions, the comments in the generated output code have a trailing comment line.

The issue may be related to go-yaml/yaml#789.

Example

Input

"$schema": "http://json-schema.org/draft-04/schema#"
"$id": "https://example.com/yaml_formatting"

title: MyObject
type: object
properties:
  foo:
    description: >
      I'm a multiline description in a folded block. Folded blocks should not
      have hard line breaks after parsing. They should also not end in a line break.
    type: string
  bar:
    description: |
      I'm a multiline description in a literal block. Literal blocks, on the other hand,
      should have hard line breaks and may end in a line break, too.

      This may look funky when Go code is generated to a specific line width, though.
    type: string

Expected Output

// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.

package test

type YamlMultilineDescriptionsYaml struct {
	// I'm a multiline description in a literal block. Literal blocks, on the other
	// hand,
	// should have hard line breaks and may end in a line break, too.
	//
	// This may look funky when Go code is generated to a specific line width, though.
	//
	Bar *string `json:"bar,omitempty" yaml:"bar,omitempty"`

	// I'm a multiline description in a folded block. Folded blocks should not have
	// hard line breaks after parsing. They should also not end in a line break.
	Foo *string `json:"foo,omitempty" yaml:"foo,omitempty"`
}

Actual Output

// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.

package test

type YamlMultilineDescriptionsYaml struct {
	// I'm a multiline description in a literal block. Literal blocks, on the other
	// hand,
	// should have hard line breaks and may end in a line break, too.
	//
	// This may look funky when Go code is generated to a specific line width, though.
	//
	Bar *string `json:"bar,omitempty" yaml:"bar,omitempty"`

	// I'm a multiline description in a folded block. Folded blocks should not have
	// hard line breaks after parsing. They should also not end in a line break.
	//
	Foo *string `json:"foo,omitempty" yaml:"foo,omitempty"`
}

Handling duplicate types

I have multiple types referencing to the another type. Currently the generator will just increment the counter after the type (say "example_1").

Is there a way to disable this behaviour. Maybe using a config option?

Question about UnmarshalJSON on non-required fields

with the following schema partial

  "type": "object",
  "properties": {
    "label": {
      "type": "string"
    }
  },
  "required": ["label"],

An UnmarshalJSON() is saved in the output file that will populate Label with the string from the JSON file. Which looks like:

type MyThing struct {
	// Label corresponds to the JSON schema field "label".
	Label string `json:"label"`
}

When required is missing, so is the UnmarshalJSON() and the resulting output file has a *string, e.g.

type MyThing struct {
	// Label corresponds to the JSON schema field "label".
	Label *string `json:"label"`
}

I've only started playing with golang today, so this may be a really obvious thing and my lack of understanding and familiarity with JSON marshalling is woefully incomplete.

But, wouldn't if be better if the UnmarshallJSON() was always created in the output and either

  • if field was optional
    • initialised it when it existed
  • if field was required
    • initialise it
    • or errored if it didn't exist

I Hope that makes sense, once again, golang is all very new to me, any quite possibly its just a total lack of understanding of how to use pointers in go, and everything is doing what its supposed to. So apologies if this is just a really dumb questions.

improvement: support format when the type is integer

Hi
I think we can support format when the type is integer.
In current version, we hard code the integer to "int" when the type is integer. but in fact. we have some others formats for integer, such as int8, uint8, int16 and so on.

such as following schema definition

           "attributeName": {
              "type": "integer",
              "format": "uint8",
              "default": "3"
            },

panic if json schema contains a field with type array without items definition

Fails with panic on nil pointer:

{
  "definitions": {},
  "$schema": "http://json-schema.org/draft-06/schema#",
  "$id": "whatever",
  "type": "object",
  "title": "The Root Schema",
  "properties": {
    "blockedEngines": {
      "type": "array",
      "title": "The Blockedengines Schema"
    }
  }
}

However, the "items" property is not mandatory.

This works:

{
  "definitions": {},
  "$schema": "http://json-schema.org/draft-06/schema#",
  "$id": "whatever",
  "type": "object",
  "title": "The Root Schema",
  "properties": {
    "blockedEngines": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "title": "The Blockedengines Schema"
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.