diff --git a/README.md b/README.md index 1a68a09..e5c64a1 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,7 @@ Sometimes it can be useful to have custom JSON Marshal and Unmarshal methods in This library will recognize and attempt to call four different methods that help you adjust schemas to your specific needs: - `JSONSchema() *Schema` - will prevent auto-generation of the schema so that you can provide your own definition. +- `JSONSchema(reflect func(t any) *Schema) *Schema` - like the `JSONSchema()` method, will prevent auto-generation so that you can provide your own schema, however it also provides a `reflect` callback which can be used to reflect over children of the parent. - `JSONSchemaExtend(schema *jsonschema.Schema)` - will be called _after_ the schema has been generated, allowing you to add or manipulate the fields easily. - `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. diff --git a/fixtures/schema_interceptor.json b/fixtures/schema_interceptor.json new file mode 100644 index 0000000..7f7f2fd --- /dev/null +++ b/fixtures/schema_interceptor.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/invopop/jsonschema/interceptor-object-a", + "$ref": "#/$defs/InterceptorObjectA", + "$defs": { + "InterceptorObjectA": { + "$ref": "#/$defs/InterceptorObjectB" + }, + "InterceptorObjectB": { + "properties": { + "obj_c": { + "$ref": "#/$defs/InterceptorObjectC" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "obj_c" + ] + }, + "InterceptorObjectC": { + "properties": { + "prop_c": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "prop_c" + ] + } + } +} diff --git a/reflect.go b/reflect.go index 0be6cfe..027696a 100644 --- a/reflect.go +++ b/reflect.go @@ -24,6 +24,13 @@ type customSchemaImpl interface { JSONSchema() *Schema } +// customInterceptorSchemaImpl is used in a similar fashion to customSchemaImpl, +// however it provides reflect callback so that you only need to provide the alternate +// schema for the current node, and can hand reflection of children back to jsonschema. +type customInterceptorSchemaImpl interface { + JSONSchema(func(any) *Schema) *Schema +} + // Function to be run after the schema has been generated. // this will let you modify a schema afterwards type extendSchemaImpl interface { @@ -47,6 +54,7 @@ var customAliasSchema = reflect.TypeOf((*aliasSchemaImpl)(nil)).Elem() var customPropertyAliasSchema = reflect.TypeOf((*propertyAliasSchemaImpl)(nil)).Elem() var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem() +var customInterceptorType = reflect.TypeOf((*customInterceptorSchemaImpl)(nil)).Elem() var extendType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem() // customSchemaGetFieldDocString @@ -367,6 +375,19 @@ func (r *Reflector) reflectCustomSchema(definitions Definitions, t reflect.Type) return st } + if t.Implements(customInterceptorType) { + v := reflect.New(t) + o := v.Interface().(customInterceptorSchemaImpl) + st := o.JSONSchema(func(t any) *Schema { + return r.refOrReflectTypeToSchema(definitions, reflect.TypeOf(t)) + }) + r.addDefinition(definitions, t, st) + if ref := r.refDefinition(definitions, t); ref != nil { + return ref + } + return st + } + return nil } diff --git a/reflect_test.go b/reflect_test.go index 37ea18a..9d8fa78 100644 --- a/reflect_test.go +++ b/reflect_test.go @@ -675,3 +675,24 @@ func TestJSONSchemaAlias(t *testing.T) { compareSchemaOutput(t, "fixtures/schema_alias.json", r, &AliasObjectB{}) compareSchemaOutput(t, "fixtures/schema_alias_2.json", r, &AliasObjectC{}) } + +type InterceptorObjectA struct { + PropA string `json:"prop_a"` +} + +type InterceptorObjectB struct { + ObjC InterceptorObjectC `json:"obj_c"` +} + +type InterceptorObjectC struct { + PropC string `json:"prop_c"` +} + +func (InterceptorObjectA) JSONSchema(reflect func(any) *Schema) *Schema { + return reflect(InterceptorObjectB{}) +} + +func TestJsonSchemaInterceptor(t *testing.T) { + r := &Reflector{} + compareSchemaOutput(t, "fixtures/schema_interceptor.json", r, &InterceptorObjectA{}) +}