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
|
||||
|
||||
A JSON schema code generator for Go.
|
||||
A [JSON schema] code generator for Go.
|
||||
|
||||
JSON schema draft 2020-12 is supported.
|
||||
|
||||
## Usage
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
if schema.Type != "" {
|
||||
return schema.Type
|
||||
switch {
|
||||
case len(schema.Type) == 1:
|
||||
return schema.Type[0]
|
||||
case len(schema.Type) > 0:
|
||||
return ""
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
|
@ -125,9 +128,40 @@ func noAdditionalProps(schema *jsonschema.Schema) bool {
|
|||
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 {
|
||||
if schema == nil {
|
||||
return jen.Interface()
|
||||
schema = &jsonschema.Schema{}
|
||||
}
|
||||
|
||||
refName := refName(schema.Ref)
|
||||
|
@ -140,6 +174,10 @@ func generateSchemaType(schema *jsonschema.Schema, root *jsonschema.Schema, requ
|
|||
return t
|
||||
}
|
||||
|
||||
if subschema, ok := unwrapNullableSchema(schema); ok {
|
||||
return jen.Op("*").Add(generateSchemaType(subschema, root, true))
|
||||
}
|
||||
|
||||
switch schemaType(schema) {
|
||||
case jsonschema.TypeNull:
|
||||
return jen.Struct()
|
||||
|
@ -148,7 +186,7 @@ func generateSchemaType(schema *jsonschema.Schema, root *jsonschema.Schema, requ
|
|||
case jsonschema.TypeArray:
|
||||
return jen.Index().Add(generateSchemaType(schema.Items, root, required))
|
||||
case jsonschema.TypeNumber:
|
||||
return jen.Float64()
|
||||
return jen.Qual("encoding/json", "Number")
|
||||
case jsonschema.TypeString:
|
||||
return jen.String()
|
||||
case jsonschema.TypeInteger:
|
||||
|
@ -189,6 +227,15 @@ func generateDef(schema *jsonschema.Schema, root *jsonschema.Schema, f *jen.File
|
|||
for _, child := range schema.OneOf {
|
||||
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 {
|
||||
refName := refName(child.Ref)
|
||||
|
|
23
schema.go
23
schema.go
|
@ -17,6 +17,25 @@ const (
|
|||
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 {
|
||||
// Core
|
||||
Schema string `json:"$schema"`
|
||||
|
@ -51,7 +70,7 @@ type Schema struct {
|
|||
PropertyNames *Schema `json:"propertyNames"`
|
||||
|
||||
// Validation
|
||||
Type Type `json:"type"`
|
||||
Type TypeSet `json:"type"`
|
||||
Enum []interface{} `json:"enum"`
|
||||
Const interface{} `json:"const"`
|
||||
|
||||
|
@ -92,7 +111,7 @@ type Schema struct {
|
|||
|
||||
func (schema *Schema) UnmarshalJSON(b []byte) error {
|
||||
if bytes.Equal(b, []byte("true")) {
|
||||
// Nothing to do
|
||||
*schema = Schema{}
|
||||
} else if bytes.Equal(b, []byte("false")) {
|
||||
*schema = Schema{Not: []Schema{
|
||||
Schema{},
|
||||
|
|
Loading…
Reference in a new issue