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 }