Compare commits

...

10 commits

Author SHA1 Message Date
Simon Ser
e0d73b5371 Consistently use json.RawMessage 2023-02-24 17:01:53 +01:00
Simon Ser
221c18e60a Special-case nullable schema 2023-02-24 16:53:44 +01:00
Simon Ser
50881d18fa readme: add links to mailing list and IRC 2023-02-16 16:31:30 +01:00
Simon Ser
b8a10fdb3a Handle arrays in schema type 2022-10-10 19:17:30 +02:00
Simon Ser
1bfe91634c readme: specify supported version of the JSON schema spec 2022-10-10 13:16:08 +02:00
Simon Ser
c28d6655f0 readme: add link to JSON schema website 2022-10-07 12:30:45 +02:00
Simon Ser
253c170dbe Generate helpers for then, else and dependantSchemas 2022-10-07 12:10:12 +02:00
Simon Ser
1e958fddb5 Reset schema when unmarshalling "true" 2022-10-07 12:02:35 +02:00
Simon Ser
7b432ffe7f readme: describe generation rules 2022-10-07 11:55:30 +02:00
Simon Ser
3fd30c72bc Use json.Number for numbers
To avoid precision loss.
2022-10-07 11:51:37 +02:00
3 changed files with 93 additions and 7 deletions

View file

@ -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

View file

@ -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)

View file

@ -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{},