github.com/alecthomas/jsonschema@v0.0.0-20220216202328-9eeeec9d044b/reflect.go (about)

     1  // Package jsonschema uses reflection to generate JSON Schemas from Go types [1].
     2  //
     3  // If json tags are present on struct fields, they will be used to infer
     4  // property names and if a property is required (omitempty is present).
     5  //
     6  // [1] http://json-schema.org/latest/json-schema-validation.html
     7  package jsonschema
     8  
     9  import (
    10  	"encoding/json"
    11  	"net"
    12  	"net/url"
    13  	"reflect"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/iancoleman/orderedmap"
    19  )
    20  
    21  // Version is the JSON Schema version.
    22  // If extending JSON Schema with custom values use a custom URI.
    23  // RFC draft-wright-json-schema-00, section 6
    24  var Version = "http://json-schema.org/draft-04/schema#"
    25  
    26  // Schema is the root schema.
    27  // RFC draft-wright-json-schema-00, section 4.5
    28  type Schema struct {
    29  	*Type
    30  	Definitions Definitions
    31  }
    32  
    33  // customSchemaType is used to detect if the type provides it's own
    34  // custom Schema Type definition to use instead. Very useful for situations
    35  // where there are custom JSON Marshal and Unmarshal methods.
    36  type customSchemaType interface {
    37  	JSONSchemaType() *Type
    38  }
    39  
    40  var customType = reflect.TypeOf((*customSchemaType)(nil)).Elem()
    41  
    42  // customSchemaGetFieldDocString
    43  type customSchemaGetFieldDocString interface {
    44  	GetFieldDocString(fieldName string) string
    45  }
    46  
    47  type customGetFieldDocString func(fieldName string) string
    48  
    49  var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem()
    50  
    51  // Type represents a JSON Schema object type.
    52  type Type struct {
    53  	// RFC draft-wright-json-schema-00
    54  	Version string `json:"$schema,omitempty"` // section 6.1
    55  	Ref     string `json:"$ref,omitempty"`    // section 7
    56  	// RFC draft-wright-json-schema-validation-00, section 5
    57  	MultipleOf           int                    `json:"multipleOf,omitempty"`           // section 5.1
    58  	Maximum              int                    `json:"maximum,omitempty"`              // section 5.2
    59  	ExclusiveMaximum     bool                   `json:"exclusiveMaximum,omitempty"`     // section 5.3
    60  	Minimum              int                    `json:"minimum,omitempty"`              // section 5.4
    61  	ExclusiveMinimum     bool                   `json:"exclusiveMinimum,omitempty"`     // section 5.5
    62  	MaxLength            int                    `json:"maxLength,omitempty"`            // section 5.6
    63  	MinLength            int                    `json:"minLength,omitempty"`            // section 5.7
    64  	Pattern              string                 `json:"pattern,omitempty"`              // section 5.8
    65  	AdditionalItems      *Type                  `json:"additionalItems,omitempty"`      // section 5.9
    66  	Items                *Type                  `json:"items,omitempty"`                // section 5.9
    67  	MaxItems             int                    `json:"maxItems,omitempty"`             // section 5.10
    68  	MinItems             int                    `json:"minItems,omitempty"`             // section 5.11
    69  	UniqueItems          bool                   `json:"uniqueItems,omitempty"`          // section 5.12
    70  	MaxProperties        int                    `json:"maxProperties,omitempty"`        // section 5.13
    71  	MinProperties        int                    `json:"minProperties,omitempty"`        // section 5.14
    72  	Required             []string               `json:"required,omitempty"`             // section 5.15
    73  	Properties           *orderedmap.OrderedMap `json:"properties,omitempty"`           // section 5.16
    74  	PatternProperties    map[string]*Type       `json:"patternProperties,omitempty"`    // section 5.17
    75  	AdditionalProperties json.RawMessage        `json:"additionalProperties,omitempty"` // section 5.18
    76  	Dependencies         map[string]*Type       `json:"dependencies,omitempty"`         // section 5.19
    77  	Enum                 []interface{}          `json:"enum,omitempty"`                 // section 5.20
    78  	Type                 string                 `json:"type,omitempty"`                 // section 5.21
    79  	AllOf                []*Type                `json:"allOf,omitempty"`                // section 5.22
    80  	AnyOf                []*Type                `json:"anyOf,omitempty"`                // section 5.23
    81  	OneOf                []*Type                `json:"oneOf,omitempty"`                // section 5.24
    82  	Not                  *Type                  `json:"not,omitempty"`                  // section 5.25
    83  	Definitions          Definitions            `json:"definitions,omitempty"`          // section 5.26
    84  	// RFC draft-wright-json-schema-validation-00, section 6, 7
    85  	Title       string        `json:"title,omitempty"`       // section 6.1
    86  	Description string        `json:"description,omitempty"` // section 6.1
    87  	Default     interface{}   `json:"default,omitempty"`     // section 6.2
    88  	Format      string        `json:"format,omitempty"`      // section 7
    89  	Examples    []interface{} `json:"examples,omitempty"`    // section 7.4
    90  	// RFC draft-handrews-json-schema-validation-02, section 9.4
    91  	ReadOnly  bool `json:"readOnly,omitempty"`
    92  	WriteOnly bool `json:"writeOnly,omitempty"`
    93  	// RFC draft-wright-json-schema-hyperschema-00, section 4
    94  	Media          *Type  `json:"media,omitempty"`          // section 4.3
    95  	BinaryEncoding string `json:"binaryEncoding,omitempty"` // section 4.3
    96  
    97  	Extras map[string]interface{} `json:"-"`
    98  }
    99  
   100  // Reflect reflects to Schema from a value using the default Reflector
   101  func Reflect(v interface{}) *Schema {
   102  	return ReflectFromType(reflect.TypeOf(v))
   103  }
   104  
   105  // ReflectFromType generates root schema using the default Reflector
   106  func ReflectFromType(t reflect.Type) *Schema {
   107  	r := &Reflector{}
   108  	return r.ReflectFromType(t)
   109  }
   110  
   111  // A Reflector reflects values into a Schema.
   112  type Reflector struct {
   113  	// AllowAdditionalProperties will cause the Reflector to generate a schema
   114  	// with additionalProperties to 'true' for all struct types. This means
   115  	// the presence of additional keys in JSON objects will not cause validation
   116  	// to fail. Note said additional keys will simply be dropped when the
   117  	// validated JSON is unmarshaled.
   118  	AllowAdditionalProperties bool
   119  
   120  	// RequiredFromJSONSchemaTags will cause the Reflector to generate a schema
   121  	// that requires any key tagged with `jsonschema:required`, overriding the
   122  	// default of requiring any key *not* tagged with `json:,omitempty`.
   123  	RequiredFromJSONSchemaTags bool
   124  
   125  	// YAMLEmbeddedStructs will cause the Reflector to generate a schema that does
   126  	// not inline embedded structs. This should be enabled if the JSON schemas are
   127  	// used with yaml.Marshal/Unmarshal.
   128  	YAMLEmbeddedStructs bool
   129  
   130  	// Prefer yaml: tags over json: tags to generate the schema even if json: tags
   131  	// are present
   132  	PreferYAMLSchema bool
   133  
   134  	// ExpandedStruct will cause the toplevel definitions of the schema not
   135  	// be referenced itself to a definition.
   136  	ExpandedStruct bool
   137  
   138  	// Do not reference definitions.
   139  	// All types are still registered under the "definitions" top-level object,
   140  	// but instead of $ref fields in containing types, the entire definition
   141  	// of the contained type is inserted.
   142  	// This will cause the entire structure of types to be output in one tree.
   143  	DoNotReference bool
   144  
   145  	// Use package paths as well as type names, to avoid conflicts.
   146  	// Without this setting, if two packages contain a type with the same name,
   147  	// and both are present in a schema, they will conflict and overwrite in
   148  	// the definition map and produce bad output.  This is particularly
   149  	// noticeable when using DoNotReference.
   150  	FullyQualifyTypeNames bool
   151  
   152  	// IgnoredTypes defines a slice of types that should be ignored in the schema,
   153  	// switching to just allowing additional properties instead.
   154  	IgnoredTypes []interface{}
   155  
   156  	// TypeMapper is a function that can be used to map custom Go types to jsonschema types.
   157  	TypeMapper func(reflect.Type) *Type
   158  
   159  	// TypeNamer allows customizing of type names
   160  	TypeNamer func(reflect.Type) string
   161  
   162  	// AdditionalFields allows adding structfields for a given type
   163  	AdditionalFields func(reflect.Type) []reflect.StructField
   164  
   165  	// CommentMap is a dictionary of fully qualified go types and fields to comment
   166  	// strings that will be used if a description has not already been provided in
   167  	// the tags. Types and fields are added to the package path using "." as a
   168  	// separator.
   169  	//
   170  	// Type descriptions should be defined like:
   171  	//
   172  	//   map[string]string{"github.com/alecthomas/jsonschema.Reflector": "A Reflector reflects values into a Schema."}
   173  	//
   174  	// And Fields defined as:
   175  	//
   176  	//   map[string]string{"github.com/alecthomas/jsonschema.Reflector.DoNotReference": "Do not reference definitions."}
   177  	//
   178  	// See also: AddGoComments
   179  	CommentMap map[string]string
   180  }
   181  
   182  // Reflect reflects to Schema from a value.
   183  func (r *Reflector) Reflect(v interface{}) *Schema {
   184  	return r.ReflectFromType(reflect.TypeOf(v))
   185  }
   186  
   187  // ReflectFromType generates root schema
   188  func (r *Reflector) ReflectFromType(t reflect.Type) *Schema {
   189  	definitions := Definitions{}
   190  	if r.ExpandedStruct {
   191  		st := &Type{
   192  			Version:              Version,
   193  			Type:                 "object",
   194  			Properties:           orderedmap.New(),
   195  			AdditionalProperties: []byte("false"),
   196  		}
   197  		if r.AllowAdditionalProperties {
   198  			st.AdditionalProperties = []byte("true")
   199  		}
   200  		r.reflectStructFields(st, definitions, t)
   201  		r.reflectStruct(definitions, t)
   202  		delete(definitions, r.typeName(t))
   203  		return &Schema{Type: st, Definitions: definitions}
   204  	}
   205  
   206  	s := &Schema{
   207  		Type:        r.reflectTypeToSchema(definitions, t),
   208  		Definitions: definitions,
   209  	}
   210  	return s
   211  }
   212  
   213  // Definitions hold schema definitions.
   214  // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26
   215  // RFC draft-wright-json-schema-validation-00, section 5.26
   216  type Definitions map[string]*Type
   217  
   218  // Available Go defined types for JSON Schema Validation.
   219  // RFC draft-wright-json-schema-validation-00, section 7.3
   220  var (
   221  	timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1
   222  	ipType   = reflect.TypeOf(net.IP{})    // ipv4 and ipv6 RFC section 7.3.4, 7.3.5
   223  	uriType  = reflect.TypeOf(url.URL{})   // uri RFC section 7.3.6
   224  )
   225  
   226  // Byte slices will be encoded as base64
   227  var byteSliceType = reflect.TypeOf([]byte(nil))
   228  
   229  // Except for json.RawMessage
   230  var rawMessageType = reflect.TypeOf(json.RawMessage{})
   231  
   232  // Go code generated from protobuf enum types should fulfil this interface.
   233  type protoEnum interface {
   234  	EnumDescriptor() ([]byte, []int)
   235  }
   236  
   237  var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem()
   238  
   239  func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Type {
   240  	// Already added to definitions?
   241  	if _, ok := definitions[r.typeName(t)]; ok && !r.DoNotReference {
   242  		return &Type{Ref: "#/definitions/" + r.typeName(t)}
   243  	}
   244  
   245  	if r.TypeMapper != nil {
   246  		if t := r.TypeMapper(t); t != nil {
   247  			return t
   248  		}
   249  	}
   250  
   251  	if rt := r.reflectCustomType(definitions, t); rt != nil {
   252  		return rt
   253  	}
   254  
   255  	// jsonpb will marshal protobuf enum options as either strings or integers.
   256  	// It will unmarshal either.
   257  	if t.Implements(protoEnumType) {
   258  		return &Type{OneOf: []*Type{
   259  			{Type: "string"},
   260  			{Type: "integer"},
   261  		}}
   262  	}
   263  
   264  	// Defined format types for JSON Schema Validation
   265  	// RFC draft-wright-json-schema-validation-00, section 7.3
   266  	// TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7
   267  	if t == ipType {
   268  		// TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5
   269  		return &Type{Type: "string", Format: "ipv4"} // ipv4 RFC section 7.3.4
   270  	}
   271  
   272  	switch t.Kind() {
   273  	case reflect.Struct:
   274  		switch t {
   275  		case timeType: // date-time RFC section 7.3.1
   276  			return &Type{Type: "string", Format: "date-time"}
   277  		case uriType: // uri RFC section 7.3.6
   278  			return &Type{Type: "string", Format: "uri"}
   279  		default:
   280  			return r.reflectStruct(definitions, t)
   281  		}
   282  
   283  	case reflect.Map:
   284  		switch t.Key().Kind() {
   285  		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   286  			rt := &Type{
   287  				Type: "object",
   288  				PatternProperties: map[string]*Type{
   289  					"^[0-9]+$": r.reflectTypeToSchema(definitions, t.Elem()),
   290  				},
   291  				AdditionalProperties: []byte("false"),
   292  			}
   293  			return rt
   294  		}
   295  
   296  		rt := &Type{
   297  			Type: "object",
   298  			PatternProperties: map[string]*Type{
   299  				".*": r.reflectTypeToSchema(definitions, t.Elem()),
   300  			},
   301  		}
   302  		delete(rt.PatternProperties, "additionalProperties")
   303  		return rt
   304  
   305  	case reflect.Slice, reflect.Array:
   306  		returnType := &Type{}
   307  		if t == rawMessageType {
   308  			return &Type{
   309  				AdditionalProperties: []byte("true"),
   310  			}
   311  		}
   312  		if t.Kind() == reflect.Array {
   313  			returnType.MinItems = t.Len()
   314  			returnType.MaxItems = returnType.MinItems
   315  		}
   316  		if t.Kind() == reflect.Slice && t.Elem() == byteSliceType.Elem() {
   317  			returnType.Type = "string"
   318  			returnType.Media = &Type{BinaryEncoding: "base64"}
   319  			return returnType
   320  		}
   321  		returnType.Type = "array"
   322  		returnType.Items = r.reflectTypeToSchema(definitions, t.Elem())
   323  		return returnType
   324  
   325  	case reflect.Interface:
   326  		return &Type{
   327  			AdditionalProperties: []byte("true"),
   328  		}
   329  
   330  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
   331  		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   332  		return &Type{Type: "integer"}
   333  
   334  	case reflect.Float32, reflect.Float64:
   335  		return &Type{Type: "number"}
   336  
   337  	case reflect.Bool:
   338  		return &Type{Type: "boolean"}
   339  
   340  	case reflect.String:
   341  		return &Type{Type: "string"}
   342  
   343  	case reflect.Ptr:
   344  		return r.reflectTypeToSchema(definitions, t.Elem())
   345  	}
   346  	panic("unsupported type " + t.String())
   347  }
   348  
   349  func (r *Reflector) reflectCustomType(definitions Definitions, t reflect.Type) *Type {
   350  	if t.Kind() == reflect.Ptr {
   351  		return r.reflectCustomType(definitions, t.Elem())
   352  	}
   353  
   354  	if t.Implements(customType) {
   355  		v := reflect.New(t)
   356  		o := v.Interface().(customSchemaType)
   357  		st := o.JSONSchemaType()
   358  		definitions[r.typeName(t)] = st
   359  		if r.DoNotReference {
   360  			return st
   361  		} else {
   362  			return &Type{
   363  				Version: Version,
   364  				Ref:     "#/definitions/" + r.typeName(t),
   365  			}
   366  		}
   367  	}
   368  
   369  	return nil
   370  }
   371  
   372  // Reflects a struct to a JSON Schema type.
   373  func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type) *Type {
   374  	if st := r.reflectCustomType(definitions, t); st != nil {
   375  		return st
   376  	}
   377  
   378  	for _, ignored := range r.IgnoredTypes {
   379  		if reflect.TypeOf(ignored) == t {
   380  			st := &Type{
   381  				Type:                 "object",
   382  				Properties:           orderedmap.New(),
   383  				AdditionalProperties: []byte("true"),
   384  			}
   385  			definitions[r.typeName(t)] = st
   386  
   387  			if r.DoNotReference {
   388  				return st
   389  			} else {
   390  				return &Type{
   391  					Version: Version,
   392  					Ref:     "#/definitions/" + r.typeName(t),
   393  				}
   394  			}
   395  		}
   396  	}
   397  
   398  	st := &Type{
   399  		Type:                 "object",
   400  		Properties:           orderedmap.New(),
   401  		AdditionalProperties: []byte("false"),
   402  		Description:          r.lookupComment(t, ""),
   403  	}
   404  	if r.AllowAdditionalProperties {
   405  		st.AdditionalProperties = []byte("true")
   406  	}
   407  	definitions[r.typeName(t)] = st
   408  	r.reflectStructFields(st, definitions, t)
   409  
   410  	if r.DoNotReference {
   411  		return st
   412  	} else {
   413  		return &Type{
   414  			Version: Version,
   415  			Ref:     "#/definitions/" + r.typeName(t),
   416  		}
   417  	}
   418  }
   419  
   420  func (r *Reflector) reflectStructFields(st *Type, definitions Definitions, t reflect.Type) {
   421  	if t.Kind() == reflect.Ptr {
   422  		t = t.Elem()
   423  	}
   424  	if t.Kind() != reflect.Struct {
   425  		return
   426  	}
   427  
   428  	var getFieldDocString customGetFieldDocString
   429  	if t.Implements(customStructGetFieldDocString) {
   430  		v := reflect.New(t)
   431  		o := v.Interface().(customSchemaGetFieldDocString)
   432  		getFieldDocString = o.GetFieldDocString
   433  	}
   434  
   435  	handleField := func(f reflect.StructField) {
   436  		name, shouldEmbed, required, nullable := r.reflectFieldName(f)
   437  		// if anonymous and exported type should be processed recursively
   438  		// current type should inherit properties of anonymous one
   439  		if name == "" {
   440  			if shouldEmbed {
   441  				r.reflectStructFields(st, definitions, f.Type)
   442  			}
   443  			return
   444  		}
   445  
   446  		property := r.reflectTypeToSchema(definitions, f.Type)
   447  		property.structKeywordsFromTags(f, st, name)
   448  		if property.Description == "" {
   449  			property.Description = r.lookupComment(t, f.Name)
   450  		}
   451  		if getFieldDocString != nil {
   452  			property.Description = getFieldDocString(f.Name)
   453  		}
   454  
   455  		if nullable {
   456  			property = &Type{
   457  				OneOf: []*Type{
   458  					property,
   459  					{
   460  						Type: "null",
   461  					},
   462  				},
   463  			}
   464  		}
   465  
   466  		st.Properties.Set(name, property)
   467  		if required {
   468  			st.Required = append(st.Required, name)
   469  		}
   470  	}
   471  
   472  	for i := 0; i < t.NumField(); i++ {
   473  		f := t.Field(i)
   474  		handleField(f)
   475  	}
   476  	if r.AdditionalFields != nil {
   477  		if af := r.AdditionalFields(t); af != nil {
   478  			for _, sf := range af {
   479  				handleField(sf)
   480  			}
   481  		}
   482  	}
   483  }
   484  
   485  func (r *Reflector) lookupComment(t reflect.Type, name string) string {
   486  	if r.CommentMap == nil {
   487  		return ""
   488  	}
   489  
   490  	n := fullyQualifiedTypeName(t)
   491  	if name != "" {
   492  		n = n + "." + name
   493  	}
   494  
   495  	return r.CommentMap[n]
   496  }
   497  
   498  func (t *Type) structKeywordsFromTags(f reflect.StructField, parentType *Type, propertyName string) {
   499  	t.Description = f.Tag.Get("jsonschema_description")
   500  	tags := strings.Split(f.Tag.Get("jsonschema"), ",")
   501  	t.genericKeywords(tags, parentType, propertyName)
   502  	switch t.Type {
   503  	case "string":
   504  		t.stringKeywords(tags)
   505  	case "number":
   506  		t.numbericKeywords(tags)
   507  	case "integer":
   508  		t.numbericKeywords(tags)
   509  	case "array":
   510  		t.arrayKeywords(tags)
   511  	}
   512  	extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",")
   513  	t.extraKeywords(extras)
   514  }
   515  
   516  // read struct tags for generic keyworks
   517  func (t *Type) genericKeywords(tags []string, parentType *Type, propertyName string) {
   518  	for _, tag := range tags {
   519  		nameValue := strings.Split(tag, "=")
   520  		if len(nameValue) == 2 {
   521  			name, val := nameValue[0], nameValue[1]
   522  			switch name {
   523  			case "title":
   524  				t.Title = val
   525  			case "description":
   526  				t.Description = val
   527  			case "type":
   528  				t.Type = val
   529  			case "oneof_required":
   530  				var typeFound *Type
   531  				for i := range parentType.OneOf {
   532  					if parentType.OneOf[i].Title == nameValue[1] {
   533  						typeFound = parentType.OneOf[i]
   534  					}
   535  				}
   536  				if typeFound == nil {
   537  					typeFound = &Type{
   538  						Title:    nameValue[1],
   539  						Required: []string{},
   540  					}
   541  					parentType.OneOf = append(parentType.OneOf, typeFound)
   542  				}
   543  				typeFound.Required = append(typeFound.Required, propertyName)
   544  			case "oneof_type":
   545  				if t.OneOf == nil {
   546  					t.OneOf = make([]*Type, 0, 1)
   547  				}
   548  				t.Type = ""
   549  				types := strings.Split(nameValue[1], ";")
   550  				for _, ty := range types {
   551  					t.OneOf = append(t.OneOf, &Type{
   552  						Type: ty,
   553  					})
   554  				}
   555  			case "enum":
   556  				switch t.Type {
   557  				case "string":
   558  					t.Enum = append(t.Enum, val)
   559  				case "integer":
   560  					i, _ := strconv.Atoi(val)
   561  					t.Enum = append(t.Enum, i)
   562  				case "number":
   563  					f, _ := strconv.ParseFloat(val, 64)
   564  					t.Enum = append(t.Enum, f)
   565  				}
   566  			}
   567  		}
   568  	}
   569  }
   570  
   571  // read struct tags for string type keyworks
   572  func (t *Type) stringKeywords(tags []string) {
   573  	for _, tag := range tags {
   574  		nameValue := strings.Split(tag, "=")
   575  		if len(nameValue) == 2 {
   576  			name, val := nameValue[0], nameValue[1]
   577  			switch name {
   578  			case "minLength":
   579  				i, _ := strconv.Atoi(val)
   580  				t.MinLength = i
   581  			case "maxLength":
   582  				i, _ := strconv.Atoi(val)
   583  				t.MaxLength = i
   584  			case "pattern":
   585  				t.Pattern = val
   586  			case "format":
   587  				switch val {
   588  				case "date-time", "email", "hostname", "ipv4", "ipv6", "uri":
   589  					t.Format = val
   590  					break
   591  				}
   592  			case "readOnly":
   593  				i, _ := strconv.ParseBool(val)
   594  				t.ReadOnly = i
   595  			case "writeOnly":
   596  				i, _ := strconv.ParseBool(val)
   597  				t.WriteOnly = i
   598  			case "default":
   599  				t.Default = val
   600  			case "example":
   601  				t.Examples = append(t.Examples, val)
   602  			}
   603  		}
   604  	}
   605  }
   606  
   607  // read struct tags for numberic type keyworks
   608  func (t *Type) numbericKeywords(tags []string) {
   609  	for _, tag := range tags {
   610  		nameValue := strings.Split(tag, "=")
   611  		if len(nameValue) == 2 {
   612  			name, val := nameValue[0], nameValue[1]
   613  			switch name {
   614  			case "multipleOf":
   615  				i, _ := strconv.Atoi(val)
   616  				t.MultipleOf = i
   617  			case "minimum":
   618  				i, _ := strconv.Atoi(val)
   619  				t.Minimum = i
   620  			case "maximum":
   621  				i, _ := strconv.Atoi(val)
   622  				t.Maximum = i
   623  			case "exclusiveMaximum":
   624  				b, _ := strconv.ParseBool(val)
   625  				t.ExclusiveMaximum = b
   626  			case "exclusiveMinimum":
   627  				b, _ := strconv.ParseBool(val)
   628  				t.ExclusiveMinimum = b
   629  			case "default":
   630  				i, _ := strconv.Atoi(val)
   631  				t.Default = i
   632  			case "example":
   633  				if i, err := strconv.Atoi(val); err == nil {
   634  					t.Examples = append(t.Examples, i)
   635  				}
   636  			}
   637  		}
   638  	}
   639  }
   640  
   641  // read struct tags for object type keyworks
   642  // func (t *Type) objectKeywords(tags []string) {
   643  //     for _, tag := range tags{
   644  //         nameValue := strings.Split(tag, "=")
   645  //         name, val := nameValue[0], nameValue[1]
   646  //         switch name{
   647  //             case "dependencies":
   648  //                 t.Dependencies = val
   649  //                 break;
   650  //             case "patternProperties":
   651  //                 t.PatternProperties = val
   652  //                 break;
   653  //         }
   654  //     }
   655  // }
   656  
   657  // read struct tags for array type keyworks
   658  func (t *Type) arrayKeywords(tags []string) {
   659  	var defaultValues []interface{}
   660  	for _, tag := range tags {
   661  		nameValue := strings.Split(tag, "=")
   662  		if len(nameValue) == 2 {
   663  			name, val := nameValue[0], nameValue[1]
   664  			switch name {
   665  			case "minItems":
   666  				i, _ := strconv.Atoi(val)
   667  				t.MinItems = i
   668  			case "maxItems":
   669  				i, _ := strconv.Atoi(val)
   670  				t.MaxItems = i
   671  			case "uniqueItems":
   672  				t.UniqueItems = true
   673  			case "default":
   674  				defaultValues = append(defaultValues, val)
   675  			case "enum":
   676  				switch t.Items.Type {
   677  				case "string":
   678  					t.Items.Enum = append(t.Items.Enum, val)
   679  				case "integer":
   680  					i, _ := strconv.Atoi(val)
   681  					t.Items.Enum = append(t.Items.Enum, i)
   682  				case "number":
   683  					f, _ := strconv.ParseFloat(val, 64)
   684  					t.Items.Enum = append(t.Items.Enum, f)
   685  				}
   686  			}
   687  		}
   688  	}
   689  	if len(defaultValues) > 0 {
   690  		t.Default = defaultValues
   691  	}
   692  }
   693  
   694  func (t *Type) extraKeywords(tags []string) {
   695  	for _, tag := range tags {
   696  		nameValue := strings.Split(tag, "=")
   697  		if len(nameValue) == 2 {
   698  			t.setExtra(nameValue[0], nameValue[1])
   699  		}
   700  	}
   701  }
   702  
   703  func (t *Type) setExtra(key, val string) {
   704  	if t.Extras == nil {
   705  		t.Extras = map[string]interface{}{}
   706  	}
   707  	if existingVal, ok := t.Extras[key]; ok {
   708  		switch existingVal := existingVal.(type) {
   709  		case string:
   710  			t.Extras[key] = []string{existingVal, val}
   711  		case []string:
   712  			t.Extras[key] = append(existingVal, val)
   713  		case int:
   714  			t.Extras[key], _ = strconv.Atoi(val)
   715  		}
   716  	} else {
   717  		switch key {
   718  		case "minimum":
   719  			t.Extras[key], _ = strconv.Atoi(val)
   720  		default:
   721  			t.Extras[key] = val
   722  		}
   723  	}
   724  }
   725  
   726  func requiredFromJSONTags(tags []string) bool {
   727  	if ignoredByJSONTags(tags) {
   728  		return false
   729  	}
   730  
   731  	for _, tag := range tags[1:] {
   732  		if tag == "omitempty" {
   733  			return false
   734  		}
   735  	}
   736  	return true
   737  }
   738  
   739  func requiredFromJSONSchemaTags(tags []string) bool {
   740  	if ignoredByJSONSchemaTags(tags) {
   741  		return false
   742  	}
   743  	for _, tag := range tags {
   744  		if tag == "required" {
   745  			return true
   746  		}
   747  	}
   748  	return false
   749  }
   750  
   751  func nullableFromJSONSchemaTags(tags []string) bool {
   752  	if ignoredByJSONSchemaTags(tags) {
   753  		return false
   754  	}
   755  	for _, tag := range tags {
   756  		if tag == "nullable" {
   757  			return true
   758  		}
   759  	}
   760  	return false
   761  }
   762  
   763  func inlineYAMLTags(tags []string) bool {
   764  	for _, tag := range tags {
   765  		if tag == "inline" {
   766  			return true
   767  		}
   768  	}
   769  	return false
   770  }
   771  
   772  func ignoredByJSONTags(tags []string) bool {
   773  	return tags[0] == "-"
   774  }
   775  
   776  func ignoredByJSONSchemaTags(tags []string) bool {
   777  	return tags[0] == "-"
   778  }
   779  
   780  func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool, bool) {
   781  	jsonTags, exist := f.Tag.Lookup("json")
   782  	yamlTags, yamlExist := f.Tag.Lookup("yaml")
   783  	if !exist || r.PreferYAMLSchema {
   784  		jsonTags = yamlTags
   785  		exist = yamlExist
   786  	}
   787  
   788  	jsonTagsList := strings.Split(jsonTags, ",")
   789  	yamlTagsList := strings.Split(yamlTags, ",")
   790  
   791  	if ignoredByJSONTags(jsonTagsList) {
   792  		return "", false, false, false
   793  	}
   794  
   795  	jsonSchemaTags := strings.Split(f.Tag.Get("jsonschema"), ",")
   796  	if ignoredByJSONSchemaTags(jsonSchemaTags) {
   797  		return "", false, false, false
   798  	}
   799  
   800  	name := f.Name
   801  	required := requiredFromJSONTags(jsonTagsList)
   802  
   803  	if r.RequiredFromJSONSchemaTags {
   804  		required = requiredFromJSONSchemaTags(jsonSchemaTags)
   805  	}
   806  
   807  	nullable := nullableFromJSONSchemaTags(jsonSchemaTags)
   808  
   809  	if jsonTagsList[0] != "" {
   810  		name = jsonTagsList[0]
   811  	}
   812  
   813  	// field not anonymous and not export has no export name
   814  	if !f.Anonymous && f.PkgPath != "" {
   815  		name = ""
   816  	}
   817  
   818  	embed := false
   819  
   820  	// field anonymous but without json tag should be inherited by current type
   821  	if f.Anonymous && !exist {
   822  		if !r.YAMLEmbeddedStructs {
   823  			name = ""
   824  			embed = true
   825  		} else {
   826  			name = strings.ToLower(name)
   827  		}
   828  	}
   829  
   830  	if yamlExist && inlineYAMLTags(yamlTagsList) {
   831  		name = ""
   832  		embed = true
   833  	}
   834  
   835  	return name, embed, required, nullable
   836  }
   837  
   838  func (s *Schema) MarshalJSON() ([]byte, error) {
   839  	b, err := json.Marshal(s.Type)
   840  	if err != nil {
   841  		return nil, err
   842  	}
   843  	if s.Definitions == nil || len(s.Definitions) == 0 {
   844  		return b, nil
   845  	}
   846  	d, err := json.Marshal(struct {
   847  		Definitions Definitions `json:"definitions,omitempty"`
   848  	}{s.Definitions})
   849  	if err != nil {
   850  		return nil, err
   851  	}
   852  	if len(b) == 2 {
   853  		return d, nil
   854  	} else {
   855  		b[len(b)-1] = ','
   856  		return append(b, d[1:]...), nil
   857  	}
   858  }
   859  
   860  func (t *Type) MarshalJSON() ([]byte, error) {
   861  	type Type_ Type
   862  	b, err := json.Marshal((*Type_)(t))
   863  	if err != nil {
   864  		return nil, err
   865  	}
   866  	if t.Extras == nil || len(t.Extras) == 0 {
   867  		return b, nil
   868  	}
   869  	m, err := json.Marshal(t.Extras)
   870  	if err != nil {
   871  		return nil, err
   872  	}
   873  	if len(b) == 2 {
   874  		return m, nil
   875  	} else {
   876  		b[len(b)-1] = ','
   877  		return append(b, m[1:]...), nil
   878  	}
   879  }
   880  
   881  func (r *Reflector) typeName(t reflect.Type) string {
   882  	if r.TypeNamer != nil {
   883  		if name := r.TypeNamer(t); name != "" {
   884  			return name
   885  		}
   886  	}
   887  	if r.FullyQualifyTypeNames {
   888  		return fullyQualifiedTypeName(t)
   889  	}
   890  	return t.Name()
   891  }
   892  
   893  func fullyQualifiedTypeName(t reflect.Type) string {
   894  	return t.PkgPath() + "." + t.Name()
   895  }
   896  
   897  // AddGoComments will update the reflectors comment map with all the comments
   898  // found in the provided source directories. See the #ExtractGoComments method
   899  // for more details.
   900  func (r *Reflector) AddGoComments(base, path string) error {
   901  	if r.CommentMap == nil {
   902  		r.CommentMap = make(map[string]string)
   903  	}
   904  	return ExtractGoComments(base, path, r.CommentMap)
   905  }