Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflector 's Mapper callback #67

Closed
tpoxa opened this issue Jan 12, 2023 · 2 comments
Closed

Reflector 's Mapper callback #67

tpoxa opened this issue Jan 12, 2023 · 2 comments

Comments

@tpoxa
Copy link
Contributor

tpoxa commented Jan 12, 2023

Hey. I am really want to migrate to your library cause it has so much perks but I think I am missing some kind of a Reflector's callback which will allow to generate Schema depends on a reflect.Type of a variable.

I saw map of types associations but unfortunately it is not an option, cause I do custom types based on a type any which I check using .Kind in the callback. So reflector does not know about exact name of a type.

CollectionDefinitions could be option but it gets definition name and schema.

What other thing I can try with?

Here is the program based on invopop's library which I am trying to migrate from.

package main

import (
	"encoding/json"
	"fmt"
	"github.com/invopop/jsonschema"
	"reflect"
)

type CustomParameter any

// User is used as a base to provide tests for comments.
type User struct {
	Param interface{} `json:"param"`

	// Unique sequential identifier.
	// Name of the user
	Dynamic CustomParameter `json:"dynamic" jsonschema:"title=Test"`
	//Some interface{} `json:"some,omitempty" jsonschema_extras:"requiredWhen=[1,2,3]"`
}

func main() {

	definitions := jsonschema.Definitions{}

	r := jsonschema.Reflector{
		Anonymous: true,
		Mapper: func(r reflect.Type) *jsonschema.Schema {
			if r.Name() == "" {
				return nil
			}
			if r.Kind() == reflect.Interface {
				definitions[r.Name()] = &jsonschema.Schema{
					Type: "object",
					Extras: map[string]interface{}{
						"configurable": true,
					},
				}
				return &jsonschema.Schema{
					Ref: fmt.Sprintf("#/$defs/%s", r.Name()),
				}
			}
			return nil
		},
		AllowAdditionalProperties: true,
	}

	sh := r.Reflect(&User{})

	for k, v := range definitions {
		sh.Definitions[k] = v
	}
	j, _ := json.MarshalIndent(sh, "", " ")
	fmt.Println(string(j))

}

The result. I highlighted important points for me

{
 "$schema": "https://json-schema.org/draft/2020-12/schema",
 "$ref": "#/$defs/User",
 "$defs": {
  "CustomParameter": { <-- definition based on a custom type name 
   "type": "object",
   "configurable": true <- I need to have this custom prop based on a kind,  using json tags might also work...
  },
  "User": {
   "properties": {
    "param": true,
    "dynamic": {
     "$ref": "#/$defs/CustomParameter",
     "title": "Test" <-- would be good to have ability add this to override for ad-hoc overrides of definition's title
    }
   },
   "type": "object",
   "required": [
    "param",
    "dynamic"
   ]
  }
 }
}

Thank you.

@vearutop
Copy link
Member

vearutop commented Jan 12, 2023

There is an InterceptType option that allows schema customization based on its reflect.Value.
(All available options are listed at https://pkg.go.dev/github.com/swaggest/jsonschema-go#Reflector.Reflect)

https://go.dev/play/p/hZgF5vsks1R

package main

import (
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/swaggest/jsonschema-go"
)

func main() {
	type CustomParameter interface{}

	// User is used as a base to provide tests for comments.
	type User struct {
		Param interface{} `json:"param" title:"Param"`

		// Unique sequential identifier.
		// Name of the user
		Dynamic CustomParameter `json:"dynamic" title:"Test"`
		//Some interface{} `json:"some,omitempty" jsonschema_extras:"requiredWhen=[1,2,3]"`
	}

	r := jsonschema.Reflector{}

	defs := map[string]jsonschema.Schema{}

	r.DefaultOptions = append(r.DefaultOptions,
		jsonschema.DefinitionsPrefix("#/$defs/"),
		jsonschema.CollectDefinitions(func(name string, schema jsonschema.Schema) {
			defs[name] = schema
		}),
		jsonschema.InterceptType(func(value reflect.Value, schema *jsonschema.Schema) (bool, error) {
			r := value.Type()

			// Nil interfaces arrive as pointer wraps.
			if r.Kind() == reflect.Ptr {
				r = r.Elem()
			}

			if r.Name() == "" {
				return false, nil
			}

			if r.Kind() == reflect.Interface {
				// Making new definition.
				s := jsonschema.Schema{}
				s.AddType(jsonschema.Object)
				s.WithExtraPropertiesItem("configurable", true)
				defs[r.Name()] = s

				// Replacing current schema with reference.
				rs := jsonschema.Schema{}
				rs.WithRef(fmt.Sprintf("#/$defs/%s", r.Name()))
				*schema = rs

				// Alternatively, in this case schema can be updated instead of replacing,
				// because it would be empty for an interface.
				//
				//schema.WithRef(fmt.Sprintf("#/$defs/%s", r.Name()))

				// True return disables further schema processing.
				return true, nil
			}

			return false, nil
		}))

	sh, _ := r.Reflect(User{})
	sh.WithExtraPropertiesItem("$defs", defs)

	j, _ := json.MarshalIndent(sh, "", " ")
	fmt.Println(string(j))
}
{
 "properties": {
  "dynamic": {
   "$ref": "#/$defs/CustomParameter",
   "title": "Test"
  },
  "param": {
   "title": "Param"
  }
 },
 "type": "object",
 "$defs": {
  "CustomParameter": {
   "type": "object",
   "configurable": true
  }
 }
}

@tpoxa
Copy link
Contributor Author

tpoxa commented Jan 12, 2023

That's awesome. Thanks @vearutop

@tpoxa tpoxa closed this as completed Jan 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants