swaggest / jsonschema-go Goto Github PK
View Code? Open in Web Editor NEWJSON Schema mapping for Go
Home Page: https://pkg.go.dev/github.com/swaggest/jsonschema-go
License: MIT License
JSON Schema mapping for Go
Home Page: https://pkg.go.dev/github.com/swaggest/jsonschema-go
License: MIT License
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.
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.
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"
}
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?
Trying to migrate from another similar library.
Have such of functionality broken. I used to think that they renamed to definitions
to $defs
to match $ref
aren't they?
https://json-schema.org/draft/2020-12/json-schema-core.html
Thanks
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...
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.
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
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
[]bytes
, including filling in the defaultsIf this is possible, I'd love a bit of guidance on how to do that ๐
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.
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.
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.
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
type Condition struct {
Param bool
}
func (d Condition) JSONSchema() (jsonschema.Schema, error) {
//custom logic based on d.Param
return jsonschema.Schema{}, nil
}
schema, _ := reflector.Reflect(Condition{Param:true})
Will I get an access to the real value of Param in the implementation? I this example it should be true
Thanks.
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.
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"
}
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"
}
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?
Lines 229 to 244 in d4b7536
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.
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.