From 563c44175ea76cde549a30884abe53a335921a89 Mon Sep 17 00:00:00 2001 From: olistrik Date: Thu, 18 Jan 2024 19:11:47 +0100 Subject: [PATCH 1/2] feat: implement interface for alternative JSONSchema() function --- README.md | 1 + reflect.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) 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/reflect.go b/reflect.go index 3249c8c..cb1d970 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 } From 7c61b8b545966aaf6952831cb9957dee269b01a9 Mon Sep 17 00:00:00 2001 From: olistrik Date: Wed, 21 Feb 2024 14:35:50 +0100 Subject: [PATCH 2/2] chore: add tests for alternate JSONSchemaExtend interceptor interface --- fixtures/schema_interceptor.json | 34 ++++++++++++++++++++++++++++++++ reflect_test.go | 21 ++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 fixtures/schema_interceptor.json 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_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{}) +}