Giter Site home page Giter Site logo

swaggest / jsonschema-go Goto Github PK

View Code? Open in Web Editor NEW
87.0 87.0 11.0 262 KB

JSON Schema mapping for Go

Home Page: https://pkg.go.dev/github.com/swaggest/jsonschema-go

License: MIT License

Makefile 1.25% Go 98.75%
code-generation hacktoberfest json-schema

jsonschema-go's People

Contributors

cussrox avatar danicc097 avatar fourcels avatar ghughes avatar slasyz avatar vearutop avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

jsonschema-go's Issues

Reflector 's Mapper callback

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.

Add const struct tag reflection

I've noticed that the const keyword is missing from the accepted list of tags and therefore something like this doesn't result in the Schema.Const being set:

Method string `json:"method" const:"authenticate"`

The const keyword has been added in draft 6:
https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.3

Would it be possible to add this or would you be open in accepting a PR? I believe this could be handled quite similarly to the default keyword.

support example tag on slice field.

code:

type WantExample struct {
	A string   `json:"a" example:"example of a"`
	B []string `json:"b" example:"example of bโ€˜s item"`
}

func main() {
	reflector := jsonschema.Reflector{}
	schema, err := reflector.Reflect(WantExample{})
	if err != nil {
		log.Fatal(err)
	}

	j, err := json.MarshalIndent(schema, "", " ")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(j))
}

output: ( there is no example on field "b")

{
 "properties": {
  "a": {
   "examples": [
    "example of a"
   ],
   "type": "string"
  },
  "b": {
   "items": {
    "type": "string"
   },
   "type": [
    "array",
    "null"
   ]
  }
 },
 "type": "object"
}

wanted:

{
 "properties": {
  "a": {
   "examples": [
    "example of a"
   ],
   "type": "string"
  },
  "b": {
   "items": {
    "type": "string",
    "example": "example of b's item"
   },
   "type": [
    "array",
    "null"
   ]
  }
 },
 "type": "object"
}

Example tags show problems

The following seems to be the correct parsing

type TestStruct struct {
	Time []string `json:"time,omitempty" examples:"[\"Xiao\",\"Dao\"]" example:"Xiao"`
}

func main() {
	reflector := jsonschema.Reflector{}
	schema, err := reflector.Reflect(&TestStruct{})
	if err != nil {
		log.Fatal(err)
	}

	j, err := json.MarshalIndent(schema, "", " ")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(j))
}

output

{
 "properties": {
  "time": {
   "examples": [
    "Xiao",
    "Dao"
   ],
   "items": {
    "examples": [
     "Xiao"
    ],
    "type": "string"
   },
   "type": "array"
  }
 },
 "type": "object"
}

But write the example below, and the one that comes out is different

apiSchema := jsonrpc.OpenAPI{}
	apiSchema.Reflector().SpecEns().Info.Title = "JSON-RPC Example"
	apiSchema.Reflector().SpecEns().Info.Version = "v1.2.3"

	apiSchema.Reflector().SpecEns().Info.WithDescription("This app showcases a trivial JSON-RPC API.")

	h := &jsonrpc.Handler{}
	h.OpenAPI = &apiSchema
	h.Validator = &jsonrpc.JSONSchemaValidator{}
	h.SkipResultValidation = true

	type inp struct {
		Name []string `json:"name" examples:"[\"Xiao\",\"Dao\"]" example:"Xiao"`
	}

	type out struct {
		Len int `json:"len"`
	}

	u := usecase.NewInteractor[*inp, out](func(ctx context.Context, input *inp, output *out) error {
		output.Len = len(input.Name)

		return nil
	})
	u.SetName("nameLength")

	h.Add(u)

	r := chi.NewRouter()

	r.Mount("/rpc", h)

	// Swagger UI endpoint at /docs.
	r.Method(http.MethodGet, "/docs/openapi.json", h.OpenAPI)

	r.Mount("/docs", v3cdn.NewHandlerWithConfig(swgui.Config{
		Title:       apiSchema.Reflector().Spec.Info.Title,
		SwaggerJSON: "/docs/openapi.json",
		BasePath:    "/docs",
		SettingsUI:  jsonrpc.SwguiSettings(nil, "/rpc"),
	}))

	// Start server.
	log.Println("http://localhost:8011/docs")

	if err := http.ListenAndServe(":8011", r); err != nil {
		log.Fatal(err)
	}

output

"Inp": {
    "type": "object",
    "properties": {
     "name": {
      "type": "array",
      "items": {
       "type": "string",
       "example": "Xiao"
      },
      "nullable": true,
      "example": "Xiao"
     }
    }
   },

Why are the examples parsed out of these two writing methods different?

"reflect: NumField of non-struct type `*yodb.DateTime`" (which means `*time.Time`)

Sorry for the terse panic report =)

type DateTime time.Time, which makes *DateTime structurally *time.Time.

Here's the stack trace:

Stack:
	 2  0x00000000004add90 in reflect.(*rtype).NumField
	     at /usr/lib/go/src/reflect/type.go:762
	 3  0x0000000000fa5fd0 in github.com/swaggest/jsonschema-go.(*Reflector).makeFields
	     at /home/_/c/go/pkg/mod/github.com/swaggest/[email protected]/reflect.go:868
	 4  0x0000000000fa66c5 in github.com/swaggest/jsonschema-go.(*Reflector).walkProperties
	     at /home/_/c/go/pkg/mod/github.com/swaggest/[email protected]/reflect.go:878
	 5  0x0000000000fa4b45 in github.com/swaggest/jsonschema-go.(*Reflector).kindSwitch
	     at /home/_/c/go/pkg/mod/github.com/swaggest/[email protected]/reflect.go:696
	 6  0x0000000000fa0cb6 in github.com/swaggest/jsonschema-go.(*Reflector).reflect
	     at /home/_/c/go/pkg/mod/github.com/swaggest/[email protected]/reflect.go:485
	 7  0x0000000000f9e026 in github.com/swaggest/jsonschema-go.(*Reflector).Reflect
	     at /home/_/c/go/pkg/mod/github.com/swaggest/[email protected]/reflect.go:261
	 8  0x0000000000fe5135 in github.com/swaggest/openapi-go/openapi31.(*Reflector).parseParametersIn.func2

My first non-codebase-insider guess: makeFields might, before NumField, need something like:

[for|if] ty.Kind() == reflect.Pointer { ty = ty.Elem() }

But what boggles the mind is: it already has that! Right at the start...

How to create JSON schema based on a JSON data?

Another odd question from me.

I am considering using JSON Schema to analyse the data structure without getting the data itself.
I know that is not precisely what this module is done for, but I still wonder if you might know or have any ideas @vearutop.

Thanks.

Struct tags for array of enum values

I have a field that is an array of enum values.

The OpenAPI spec to represent this is this:

"days": {
  "type": "array",
  "items": {
    "type": "string",
    "enum": [
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday",
      "Sunday"
    ]
  }
}

I can not find how this can be represented in a go struct tag. This does not work:

Days []string `json:"days" enum:"Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday"`

It results in this incorrect (invalid?) OpenAPI schema:

"days": {
  "enum": [
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
    "Sunday"
  ],
  "type": "array",
  "items": {
    "type": "string",
  }
}

Thanks

How to validate input unmarshal to a struct

Hi! So first, thank you for making this library, and I got a quick question about usage which I think I'm not getting. So suppose that I create a jsonschema struct like in the readme example:

type MyStruct struct {
    Amount float64  `json:"amount" default:"20" minimum:"10.5" example:"20.6" required:"true"`
    Abc    string   `json:"abc" pattern:"[abc]"`
    _      struct{} `additionalProperties:"false"`                   // Tags of unnamed field are applied to parent schema.
    _      struct{} `title:"My Struct" description:"Holds my data."` // Multiple unnamed fields can be used.
}

I use it to create a schema and that's all well and good. Now, I have a []byte input given by the user supposedly for data adhering to this schema. What I want to do is

  1. Validate that the data given is valid for the expected schema
  2. Get an instance of MyStruct with the field data gotten by "unmarshalling" the given []bytes, including filling in the defaults

If this is possible, I'd love a bit of guidance on how to do that ๐Ÿ˜…

Custom schemas properties using tags

It looks like custom tags being ignored

package main

import (
	"encoding/json"
	"fmt"
	"github.com/swaggest/jsonschema-go"
)

type Recipient struct {
	Name  string `json:"name" title:"Name" default:"John Doe" colSpan:"col-span-6"`
	Email string `json:"email" required:"true" title:"Email" format:"email" default:"[email protected]" colSpan:"col-span-6"`
}

func main() {
	r := jsonschema.Reflector{}
	sh, _ := r.Reflect(Recipient{})

	j, _ := json.MarshalIndent(sh, "", " ")
	fmt.Println(string(j))
}

The result:

{
 "required": [
  "email"
 ],
 "properties": {
  "email": {
   "title": "Email",
   "default": "[email protected]",
   "type": "string",
   "format": "email"
  },
  "name": {
   "title": "Name",
   "default": "John Doe",
   "type": "string"
  }
 },
 "type": "object"
}

Any chance to have custom properties presented?

I've checked Intercept things but did not find a solution.

Thank you.

Implementing TextMarshaler overrides my Schema

Hi, I noticed that when I implement TextMarshaler on a type, the schema that I set through a Preparer interface is ignored. Looks like it gets picked up by isWellKnownType in reflect.go before PrepareJSONSchema is called.

I'd like some way similar to what was done here, #62 but to make sure it's type is not overridden because it implements TextMarshaler.

default tag does not work for arrays

Describe the bug
Default tag does not work for array fields of a struct

To Reproduce

type Recipient struct {
	Routes []string `json:"routes,omitempty" title:"Routes" default:"A,B"`
}

func main() {
	r := jsonschema.Reflector{}
	sh, _ := r.Reflect(Recipient{})

	j, _ := json.MarshalIndent(sh, "", " ")
	fmt.Println(string(j))
}

Expected behavior

{
 "properties": {
  "routes": {
   "title": "Routes",
   "items": {
    "type": "string"
   },
   "type": "array",
   "default":["A","B"]  <-- Default value
  }
 },
 "type": "object"
}

Actual behaviour

{
 "properties": {
  "routes": {
   "title": "Routes",
   "items": {
    "type": "string"
   },
   "type": "array"
  }
 },
 "type": "object"
}

Additional context
I checked with jsonschemavalidator.net that schema where default is set for an array is valid for Draft-7
I guess its quite a challenge to do for an object, but at-least basic type support would be great.

Duplicate Fields have empty definitions with `InlineRefs` enabled

Describe the bug
When using InlineRefs, multiple properties of the same type leave all but one fields without an inline definition.

To Reproduce
When running the following code:

package jstest

import (
	"encoding/json"
	"testing"

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

type ExampleEvent struct {
	ID          string `json:"id,omitempty"`
	NewData     Data   `json:"new_data"`
	CurrentData Data   `json:"current_data"`
	OldData     Data   `json:"old_data"`
}

type Data struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
}

func TestRaw(t *testing.T) {
	ref := jsonschema.Reflector{}

	gen, _ := ref.Reflect(&ExampleEvent{}, func(rc *jsonschema.ReflectContext) {
		rc.InlineRefs = true
	})
	data, _ := json.MarshalIndent(gen, "", " ")
	t.Error(string(data))
}

The following schema is produced:

{
    "properties": {
        "current_data": {},
        "id": {
            "type": "string"
        },
        "new_data": {
            "properties": {
                "id": {
                    "type": "string"
                },
                "name": {
                    "type": "string"
                }
            },
            "type": "object"
        },
        "old_data": {}
    },
    "type": "object"
}

Expected behavior
An inline definition should be produced for each of the 'current_data', 'new_data', old_data' fields

implementations of `encoding.TextMarshaler` render as a union of the base type and string, instead of just string

Describe the bug

Types that implement encoding.TextMarshaler are being rendered with a type set that has the union of the base type and string, instead of just string.

To Reproduce

import (
  "github.com/google/uuid"
  jsonschema "github.com/swaggest/jsonschema-go"
)

type T struct {
  ID         uuid.UUID `json:"id"` // uuid.UUID has type [16]byte, but implements encoding.TextMarshaler
  OptionalID uuid.UUID `json:"optional_id"`
}

func main() {
	reflector := jsonschema.Reflector{}
	schema, err := reflector.Reflect(T{})
	if err != nil {
		panic(fmt.Errorf("generating schema: %w", err))
	}

	sBytes, err := json.MarshalIndent(schema, "", "  ")
	if err != nil {
		panic(fmt.Errorf("rendering schema to json: %w", err))
	}

	fmt.Println(string(sBytes))
}

produces:

{
  "definitions": {
    "UuidUUID": {
      "items": {
        "minimum": 0,
        "type": "integer"
      },
      "type": [
        "string",
        "array",
        "null"
      ]
    }
  },
  "properties": {
    "id": {
      "$ref": "#/definitions/UuidUUID"
    },
    "optional_id": {
      "$ref": "#/definitions/UuidUUID"
    }
  },
  "type": "object"
}

Expected behavior

As uuid.UUID and *uuid.UUID implement encoding.TextMarshaler, I would expect the set of types generated to be only ["string", "null"]

{
  "definitions": {
    "UuidUUID": {
      "type": [
        "string",
        "null"
      ]
    }
  },
  "properties": {
    "id": {
      "$ref": "#/definitions/UuidUUID"
    },
    "optional_id": {
      "$ref": "#/definitions/UuidUUID"
    }
  },
  "type": "object"
}

Additional context

from encoding/json:

If no MarshalJSON method is present but the value implements encoding.TextMarshaler instead, Marshal calls its MarshalText method and encodes the result as a JSON string.

we recently upgraded from v0.3.41 to v0.3.62, and it includes this change. schemas such as *"github.com/google/uuid".UUID went from

{
  "type": [
    "null",
    "string"
  ]
}

to

{
  "items": {
    "minimum": 0,
    "type": "integer"
  },
  "type": [
    "null",
    "string",
    "array"
  ]
}

even though they're always going to be rendered as strings thanks to the behaviour of "encoding/json".Marshal & encoding.TextMarshaler

This was the behaviour until #72, where it was changed to allow for subsequent processing of additional interceptors. A side-effect of this change seems to have been that the base type ([]byte -> "array of numbers") is included in the union, instead of being replaced.

Would there be any way to make it so if the type implements encoding.TextEncoder its base type is set to string (and only string), while still allowing for additional interceptors as requested in #72?

should InterceptType be called after options applied?

jsonschema-go/reflect.go

Lines 229 to 244 in d4b7536

rc := ReflectContext{}
rc.Context = context.Background()
rc.DefinitionsPrefix = "#/definitions/"
rc.PropertyNameTag = "json"
rc.Path = []string{"#"}
rc.typeCycles = make(map[refl.TypeString]bool)
InterceptType(checkSchemaSetup)(&rc)
for _, option := range r.DefaultOptions {
option(&rc)
}
for _, option := range options {
option(&rc)
}

the rc instance does not set InterceptProperty filed, so the L236 just set rc.InterceptProperty = checkSchemaSetup.

When the options applied, the InterceptProperty would be rewrite.

Interception occurs before PrepareJSONSchema

Describe the bug

I would like my schema to include markdownDescription attributes so that VS Code (via the JSON and YAML language servers) will render help text properly in hover overlays. Since I don't want to have to maintain duplicate versions of each description, I was hoping to use InterceptSchema to populate the markdownDescription value automatically.

Unfortunately, many of my descriptions are set via PrepareJSONSchema() (they're long and not conducive to being managed in struct tags). It appears that both the property and schema interceptors are called before PrepareJSONSchema.

To Reproduce

type MySchema struct {
	Something string `yaml:"something"`
}

func (m *MySchema) PrepareJSONSchema(schema *jsonschema.Schema) error {
	schema.Properties["something"].TypeObject.WithDescription(`
		Super long description...
	`)
}

schema, err := (&jsonschema.Reflector{}).Reflect(
	&MySchema{},
	jsonschema.InterceptSchema(func(params jsonschema.InterceptSchemaParams) (bool, error) {
		if params.Processed {
			params.Schema.WithExtraPropertiesItem(
				"markdownDescription", *params.Schema.Description,
			)
		}
		return false, nil
	}),
)

Expected behavior

I expected schema to contain the markdownDescription prop set to the content of the description... sadly, it did not. I had to post-process schema with the following. Which honestly, wasn't that big of a deal. It was just confusing because it seemed like the interceptor approach was the right way to go about it.

func setMarkdownDescription(schema *jsonschema.Schema) {
	if schema.Description != nil {
		schema.WithExtraPropertiesItem(
			"markdownDescription", *schema.Description,
		)
	}
	for _, s := range schema.Properties {
		if s.TypeObject != nil {
			setMarkdownDescription(s.TypeObject)
		}
	}
	for _, s := range schema.Definitions {
		if s.TypeObject != nil {
			setMarkdownDescription(s.TypeObject)
		}
	}
}

Additional context

I don't know if this is technically a bug, or if the behavior is by design. If it's the latter, I think it would be good to document in both interceptor functions to prevent further confusion.

panic when get slice type struct

Hi,
This Code panic:

type PanicType []string

type PanicStruct struct {
	IPPolicy PanicType `json:"ip_policy" example:"127.0.0.1"`
}

func main() {
	reflector := jsonschema.Reflector{}
	schema, err := reflector.Reflect(&PanicStruct{})
	if err != nil {
		log.Fatal(err)
	}

	j, err := json.MarshalIndent(schema, "", " ")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(j))
}

When replace PanicType as []string, code run expectedly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.