Skip to content

Commit

Permalink
support for JSONSchemaAlias y JSONSchemaProperty methods
Browse files Browse the repository at this point in the history
  • Loading branch information
samlown committed Oct 4, 2023
1 parent 0108689 commit 933814a
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 31 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ This library will recognize and attempt to call three different methods that hel

- `JSONSchema() *Schema` - will prevent auto-generation of the schema so that you can provide your own definition.
- `JSONSchemaExtend(schema *jsonschema.Schema)` - will be called _after_ the schema has been generated, allowing you to add or manipulate the fields easily.
- `JSONSchemaAlias(prop string) any` - will be called for every property inside a struct giving you the chance to provide an alternative object to convert into a schema.
- `JSONSchemaAlias() any` - is called when reflecting the type of object and allows for an alternative to be used instead.
- `JSONSchemaProperty(prop string) any` - will be called for every property inside a struct giving you the chance to provide an alternative object to convert into a schema.

Note that all of these methods **must** be defined on a non-pointer object for them to be called.

Expand Down
22 changes: 5 additions & 17 deletions fixtures/schema_alias.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/alias-object-base",
"$ref": "#/$defs/AliasObjectBase",
"$id": "https://github.com/invopop/jsonschema/alias-object-b",
"$ref": "#/$defs/AliasObjectA",
"$defs": {
"AliasObjectB": {
"AliasObjectA": {
"properties": {
"prop_b": {
"prop_a": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"prop_b"
]
},
"AliasObjectBase": {
"properties": {
"object": {
"$ref": "#/$defs/AliasObjectB"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"object"
"prop_a"
]
}
}
Expand Down
31 changes: 31 additions & 0 deletions fixtures/schema_alias_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/alias-object-c",
"$ref": "#/$defs/AliasObjectC",
"$defs": {
"AliasObjectA": {
"properties": {
"prop_a": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"prop_a"
]
},
"AliasObjectC": {
"properties": {
"obj_b": {
"$ref": "#/$defs/AliasObjectA"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"obj_b"
]
}
}
}
31 changes: 31 additions & 0 deletions fixtures/schema_property_alias.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/alias-property-object-base",
"$ref": "#/$defs/AliasPropertyObjectBase",
"$defs": {
"AliasObjectA": {
"properties": {
"prop_a": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"prop_a"
]
},
"AliasPropertyObjectBase": {
"properties": {
"object": {
"$ref": "#/$defs/AliasObjectA"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"object"
]
}
}
}
32 changes: 24 additions & 8 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,21 @@ type extendSchemaImpl interface {
JSONSchemaExtend(*Schema)
}

// If an object to be reflected defines a `JSONSchemaAlias` method,
// If the object to be reflected defines a `JSONSchemaAlias` method, its type will
// be used instead of the original type.
type aliasSchemaImpl interface {
JSONSchemaAlias() any
}

// If an object to be reflected defines a `JSONSchemaPropertyAlias` method,
// it will be called for each property to determine if another object
// should be used for the contents.
type aliasSchemaImpl interface {
JSONSchemaAlias(prop string) any
type propertyAliasSchemaImpl interface {
JSONSchemaProperty(prop string) any
}

var customAliasSchema = reflect.TypeOf((*aliasSchemaImpl)(nil)).Elem()
var customPropertyAliasSchema = reflect.TypeOf((*propertyAliasSchemaImpl)(nil)).Elem()

var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem()
var extendType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem()
Expand Down Expand Up @@ -262,6 +269,15 @@ func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type)
return r.refOrReflectTypeToSchema(definitions, t.Elem())
}

// Check if the there is an alias method that provides an object
// that we should use instead of this one.
if t.Implements(customAliasSchema) {
v := reflect.New(t)
o := v.Interface().(aliasSchemaImpl)
t = reflect.TypeOf(o.JSONSchemaAlias())
return r.refOrReflectTypeToSchema(definitions, t)
}

// Do any pre-definitions exist?
if r.Mapper != nil {
if t := r.Mapper(t); t != nil {
Expand Down Expand Up @@ -466,13 +482,13 @@ func (r *Reflector) reflectStructFields(st *Schema, definitions Definitions, t r
getFieldDocString = o.GetFieldDocString
}

customAliasMethod := func(string) any {
customPropertyMethod := func(string) any {
return nil
}
if t.Implements(customAliasSchema) {
if t.Implements(customPropertyAliasSchema) {
v := reflect.New(t)
o := v.Interface().(aliasSchemaImpl)
customAliasMethod = o.JSONSchemaAlias
o := v.Interface().(propertyAliasSchemaImpl)
customPropertyMethod = o.JSONSchemaProperty
}

handleField := func(f reflect.StructField) {
Expand All @@ -489,7 +505,7 @@ func (r *Reflector) reflectStructFields(st *Schema, definitions Definitions, t r
// If a JSONSchemaAlias(prop string) method is defined, attempt to use
// the provided object's type instead of the field's type.
var property *Schema
if alias := customAliasMethod(name); alias != nil {
if alias := customPropertyMethod(name); alias != nil {
property = r.refOrReflectTypeToSchema(definitions, reflect.TypeOf(alias))
} else {
property = r.refOrReflectTypeToSchema(definitions, f.Type)
Expand Down
23 changes: 18 additions & 5 deletions reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,19 +629,32 @@ type AliasObjectA struct {
type AliasObjectB struct {
PropB string `json:"prop_b"`
}
type AliasObjectBase struct {
Object *AliasObjectA `json:"object"`
type AliasObjectC struct {
ObjB *AliasObjectB `json:"obj_b"`
}
type AliasPropertyObjectBase struct {
Object any `json:"object"`
}

func (AliasObjectBase) JSONSchemaAlias(prop string) any {
func (AliasPropertyObjectBase) JSONSchemaProperty(prop string) any {
switch prop {
case "object":
return &AliasObjectB{}
return &AliasObjectA{}
}
return nil
}

func (AliasObjectB) JSONSchemaAlias() any {
return AliasObjectA{}
}

func TestJSONSchemaProperty(t *testing.T) {
r := &Reflector{}
compareSchemaOutput(t, "fixtures/schema_property_alias.json", r, &AliasPropertyObjectBase{})
}

func TestJSONSchemaAlias(t *testing.T) {
r := &Reflector{}
compareSchemaOutput(t, "fixtures/schema_alias.json", r, &AliasObjectBase{})
compareSchemaOutput(t, "fixtures/schema_alias.json", r, &AliasObjectB{})
compareSchemaOutput(t, "fixtures/schema_alias_2.json", r, &AliasObjectC{})
}

0 comments on commit 933814a

Please sign in to comment.