Compare commits
10 commits
3252e0dde7
...
e0d73b5371
Author | SHA1 | Date | |
---|---|---|---|
|
e0d73b5371 | ||
|
221c18e60a | ||
|
50881d18fa | ||
|
b8a10fdb3a | ||
|
1bfe91634c | ||
|
c28d6655f0 | ||
|
253c170dbe | ||
|
1e958fddb5 | ||
|
7b432ffe7f | ||
|
3fd30c72bc |
3 changed files with 93 additions and 7 deletions
22
README.md
22
README.md
|
@ -1,11 +1,31 @@
|
||||||
# go-jsonschema
|
# go-jsonschema
|
||||||
|
|
||||||
A JSON schema code generator for Go.
|
A [JSON schema] code generator for Go.
|
||||||
|
|
||||||
|
JSON schema draft 2020-12 is supported.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
jsonschemagen -s <schema> -o <output>
|
jsonschemagen -s <schema> -o <output>
|
||||||
|
|
||||||
|
One Go type per definition will be generated.
|
||||||
|
|
||||||
|
- `int64` is used for `"type": "integer"`.
|
||||||
|
- `json.Number` is used for `"type": "number"`.
|
||||||
|
- Go structs are generated for objects with `"additionalProperties": false`.
|
||||||
|
- `json.RawMessage` is used when a value can have multiple types. Helpers are
|
||||||
|
generated for `allOf`, `anyOf`, `oneOf`, `then`, `else` and `dependantSchemas`
|
||||||
|
which are references.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Report bugs and send patches to the [mailing list]. Discuss in [#emersion] on
|
||||||
|
Libera Chat.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
|
[JSON schema]: https://json-schema.org/
|
||||||
|
[mailing list]: https://lists.sr.ht/~emersion/public-inbox
|
||||||
|
[#emersion]: ircs://irc.libera.chat/#emersion
|
||||||
|
|
|
@ -52,8 +52,11 @@ func resolveRef(def *jsonschema.Schema, root *jsonschema.Schema) *jsonschema.Sch
|
||||||
}
|
}
|
||||||
|
|
||||||
func schemaType(schema *jsonschema.Schema) jsonschema.Type {
|
func schemaType(schema *jsonschema.Schema) jsonschema.Type {
|
||||||
if schema.Type != "" {
|
switch {
|
||||||
return schema.Type
|
case len(schema.Type) == 1:
|
||||||
|
return schema.Type[0]
|
||||||
|
case len(schema.Type) > 0:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var v interface{}
|
var v interface{}
|
||||||
|
@ -125,9 +128,40 @@ func noAdditionalProps(schema *jsonschema.Schema) bool {
|
||||||
return schema.AdditionalProperties != nil && schema.AdditionalProperties.IsFalse()
|
return schema.AdditionalProperties != nil && schema.AdditionalProperties.IsFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unwrapNullableSchema unwraps a schema in the form:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "oneOf": {
|
||||||
|
// { "type": "null" },
|
||||||
|
// <sub-schema>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
func unwrapNullableSchema(schema *jsonschema.Schema) (*jsonschema.Schema, bool) {
|
||||||
|
for _, choices := range [][]jsonschema.Schema{schema.AnyOf, schema.OneOf} {
|
||||||
|
if len(choices) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nullIndex := -1
|
||||||
|
for i, choice := range choices {
|
||||||
|
if len(choice.Type) == 1 && choice.Type[0] == jsonschema.TypeNull {
|
||||||
|
nullIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nullIndex < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
otherIndex := (nullIndex + 1) % 2
|
||||||
|
return &choices[otherIndex], true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
func generateSchemaType(schema *jsonschema.Schema, root *jsonschema.Schema, required bool) jen.Code {
|
func generateSchemaType(schema *jsonschema.Schema, root *jsonschema.Schema, required bool) jen.Code {
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
return jen.Interface()
|
schema = &jsonschema.Schema{}
|
||||||
}
|
}
|
||||||
|
|
||||||
refName := refName(schema.Ref)
|
refName := refName(schema.Ref)
|
||||||
|
@ -140,6 +174,10 @@ func generateSchemaType(schema *jsonschema.Schema, root *jsonschema.Schema, requ
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if subschema, ok := unwrapNullableSchema(schema); ok {
|
||||||
|
return jen.Op("*").Add(generateSchemaType(subschema, root, true))
|
||||||
|
}
|
||||||
|
|
||||||
switch schemaType(schema) {
|
switch schemaType(schema) {
|
||||||
case jsonschema.TypeNull:
|
case jsonschema.TypeNull:
|
||||||
return jen.Struct()
|
return jen.Struct()
|
||||||
|
@ -148,7 +186,7 @@ func generateSchemaType(schema *jsonschema.Schema, root *jsonschema.Schema, requ
|
||||||
case jsonschema.TypeArray:
|
case jsonschema.TypeArray:
|
||||||
return jen.Index().Add(generateSchemaType(schema.Items, root, required))
|
return jen.Index().Add(generateSchemaType(schema.Items, root, required))
|
||||||
case jsonschema.TypeNumber:
|
case jsonschema.TypeNumber:
|
||||||
return jen.Float64()
|
return jen.Qual("encoding/json", "Number")
|
||||||
case jsonschema.TypeString:
|
case jsonschema.TypeString:
|
||||||
return jen.String()
|
return jen.String()
|
||||||
case jsonschema.TypeInteger:
|
case jsonschema.TypeInteger:
|
||||||
|
@ -189,6 +227,15 @@ func generateDef(schema *jsonschema.Schema, root *jsonschema.Schema, f *jen.File
|
||||||
for _, child := range schema.OneOf {
|
for _, child := range schema.OneOf {
|
||||||
children = append(children, child)
|
children = append(children, child)
|
||||||
}
|
}
|
||||||
|
if schema.Then != nil {
|
||||||
|
children = append(children, *schema.Then)
|
||||||
|
}
|
||||||
|
if schema.Else != nil {
|
||||||
|
children = append(children, *schema.Else)
|
||||||
|
}
|
||||||
|
for _, child := range schema.DependentSchemas {
|
||||||
|
children = append(children, child)
|
||||||
|
}
|
||||||
|
|
||||||
for _, child := range children {
|
for _, child := range children {
|
||||||
refName := refName(child.Ref)
|
refName := refName(child.Ref)
|
||||||
|
|
23
schema.go
23
schema.go
|
@ -17,6 +17,25 @@ const (
|
||||||
TypeInteger Type = "integer"
|
TypeInteger Type = "integer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TypeSet []Type
|
||||||
|
|
||||||
|
func (ts *TypeSet) UnmarshalJSON(b []byte) error {
|
||||||
|
if b[0] == '[' {
|
||||||
|
type rawTypeSet TypeSet
|
||||||
|
out := (*rawTypeSet)(ts)
|
||||||
|
return json.Unmarshal(b, out)
|
||||||
|
} else {
|
||||||
|
var t Type
|
||||||
|
err := json.Unmarshal(b, &t)
|
||||||
|
if err != nil {
|
||||||
|
*ts = nil
|
||||||
|
} else {
|
||||||
|
*ts = []Type{t}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Schema struct {
|
type Schema struct {
|
||||||
// Core
|
// Core
|
||||||
Schema string `json:"$schema"`
|
Schema string `json:"$schema"`
|
||||||
|
@ -51,7 +70,7 @@ type Schema struct {
|
||||||
PropertyNames *Schema `json:"propertyNames"`
|
PropertyNames *Schema `json:"propertyNames"`
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
Type Type `json:"type"`
|
Type TypeSet `json:"type"`
|
||||||
Enum []interface{} `json:"enum"`
|
Enum []interface{} `json:"enum"`
|
||||||
Const interface{} `json:"const"`
|
Const interface{} `json:"const"`
|
||||||
|
|
||||||
|
@ -92,7 +111,7 @@ type Schema struct {
|
||||||
|
|
||||||
func (schema *Schema) UnmarshalJSON(b []byte) error {
|
func (schema *Schema) UnmarshalJSON(b []byte) error {
|
||||||
if bytes.Equal(b, []byte("true")) {
|
if bytes.Equal(b, []byte("true")) {
|
||||||
// Nothing to do
|
*schema = Schema{}
|
||||||
} else if bytes.Equal(b, []byte("false")) {
|
} else if bytes.Equal(b, []byte("false")) {
|
||||||
*schema = Schema{Not: []Schema{
|
*schema = Schema{Not: []Schema{
|
||||||
Schema{},
|
Schema{},
|
||||||
|
|
Loading…
Reference in a new issue