Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions fixtures/examples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/examples",
"$ref": "#/$defs/Examples",
"$defs": {
"Examples": {
"properties": {
"string_example": {
"type": "string",
"examples": [
"hi",
"test"
]
},
"int_example": {
"type": "integer",
"examples": [
1,
10,
42
]
},
"float_example": {
"type": "number",
"examples": [
2,
3.14,
13.37
]
},
"int_array_example": {
"items": {
"type": "integer"
},
"type": "array",
"examples": [
[
1,
2
],
[
3,
4
],
[
5,
6
]
]
},
"map_example": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object",
"examples": [
{
"key": "value"
}
]
},
"map_array_example": {
"items": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
},
"type": "array",
"examples": [
[
{
"a": "b"
},
{
"c": "d"
}
],
[
{
"hello": "test"
}
]
]
}
},
"additionalProperties": false,
"type": "object",
"required": [
"string_example",
"int_example",
"float_example",
"int_array_example",
"map_example",
"map_array_example"
]
}
}
}
108 changes: 65 additions & 43 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,17 +646,66 @@ func (t *Schema) structKeywordsFromTags(f reflect.StructField, parent *Schema, p
t.numbericKeywords(tags)
case "array":
t.arrayKeywords(tags)
case "boolean":
t.booleanKeywords(tags)
}
extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",")
t.extraKeywords(extras)
}

func (t *Schema) parseValue(val string) (parsed interface{}, ok bool) {
Comment thread
ecryth marked this conversation as resolved.
Outdated
switch t.Type {
case "number":
if i, err := strconv.Atoi(val); err == nil {
return i, true
} else if f, err := strconv.ParseFloat(val, 64); err == nil {
return f, true
} else {
return nil, false
}

case "integer":
i, err := strconv.Atoi(val)
return i, err == nil

case "boolean":
if val == "true" {
return true, true
} else if val == "false" {
return false, true
} else {
return false, false
}

case "string":
return val, true

case "array":
vals := strings.Split(val, ";")
parsed := make([]interface{}, len(vals))
for i, v := range vals {
p, ok := t.Items.parseValue(v)
if !ok {
return nil, false
}
parsed[i] = p
}
return parsed, true

case "", "object":
Comment thread
ecryth marked this conversation as resolved.
Outdated
obj := make(map[string]interface{})
if err := json.Unmarshal([]byte(val), &obj); err != nil {
return nil, false
}
return obj, true

default:
return nil, false
}
}

// read struct tags for generic keyworks
func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName string) {
for _, tag := range tags {
nameValue := strings.Split(tag, "=")
nameValue := strings.SplitN(tag, "=", 2)
if len(nameValue) == 2 {
name, val := nameValue[0], nameValue[1]
switch name {
Expand Down Expand Up @@ -705,24 +754,14 @@ func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName str
f, _ := strconv.ParseFloat(val, 64)
t.Enum = append(t.Enum, f)
}
}
}
}
}

// read struct tags for boolean type keyworks
func (t *Schema) booleanKeywords(tags []string) {
for _, tag := range tags {
nameValue := strings.Split(tag, "=")
if len(nameValue) != 2 {
continue
}
name, val := nameValue[0], nameValue[1]
if name == "default" {
if val == "true" {
t.Default = true
} else if val == "false" {
t.Default = false
case "default":
if v, ok := t.parseValue(val); ok {
t.Default = v
}
case "example":
if v, ok := t.parseValue(val); ok {
t.Examples = append(t.Examples, v)
}
}
}
}
Expand All @@ -731,7 +770,7 @@ func (t *Schema) booleanKeywords(tags []string) {
// read struct tags for string type keyworks
func (t *Schema) stringKeywords(tags []string) {
for _, tag := range tags {
nameValue := strings.Split(tag, "=")
nameValue := strings.SplitN(tag, "=", 2)
if len(nameValue) == 2 {
name, val := nameValue[0], nameValue[1]
switch name {
Expand All @@ -755,10 +794,6 @@ func (t *Schema) stringKeywords(tags []string) {
case "writeOnly":
i, _ := strconv.ParseBool(val)
t.WriteOnly = i
case "default":
t.Default = val
case "example":
t.Examples = append(t.Examples, val)
}
}
}
Expand All @@ -767,7 +802,7 @@ func (t *Schema) stringKeywords(tags []string) {
// read struct tags for numberic type keyworks
func (t *Schema) numbericKeywords(tags []string) {
for _, tag := range tags {
nameValue := strings.Split(tag, "=")
nameValue := strings.SplitN(tag, "=", 2)
if len(nameValue) == 2 {
name, val := nameValue[0], nameValue[1]
switch name {
Expand All @@ -786,13 +821,6 @@ func (t *Schema) numbericKeywords(tags []string) {
case "exclusiveMinimum":
b, _ := strconv.ParseBool(val)
t.ExclusiveMinimum = b
case "default":
i, _ := strconv.Atoi(val)
t.Default = i
case "example":
if i, err := strconv.Atoi(val); err == nil {
t.Examples = append(t.Examples, i)
}
}
}
}
Expand All @@ -801,7 +829,7 @@ func (t *Schema) numbericKeywords(tags []string) {
// read struct tags for object type keyworks
// func (t *Type) objectKeywords(tags []string) {
// for _, tag := range tags{
// nameValue := strings.Split(tag, "=")
// nameValue := strings.SplitN(tag, "=", 2)
// name, val := nameValue[0], nameValue[1]
// switch name{
// case "dependencies":
Expand All @@ -816,9 +844,8 @@ func (t *Schema) numbericKeywords(tags []string) {

// read struct tags for array type keyworks
func (t *Schema) arrayKeywords(tags []string) {
var defaultValues []interface{}
for _, tag := range tags {
nameValue := strings.Split(tag, "=")
nameValue := strings.SplitN(tag, "=", 2)
if len(nameValue) == 2 {
name, val := nameValue[0], nameValue[1]
switch name {
Expand All @@ -830,8 +857,6 @@ func (t *Schema) arrayKeywords(tags []string) {
t.MaxItems = i
case "uniqueItems":
t.UniqueItems = true
case "default":
defaultValues = append(defaultValues, val)
case "enum":
switch t.Items.Type {
case "string":
Expand All @@ -848,14 +873,11 @@ func (t *Schema) arrayKeywords(tags []string) {
}
}
}
if len(defaultValues) > 0 {
t.Default = defaultValues
}
}

func (t *Schema) extraKeywords(tags []string) {
for _, tag := range tags {
nameValue := strings.Split(tag, "=")
nameValue := strings.SplitN(tag, "=", 2)
if len(nameValue) == 2 {
t.setExtra(nameValue[0], nameValue[1])
}
Expand Down
10 changes: 10 additions & 0 deletions reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,15 @@ type KeyNamed struct {
RenamedByComputation int `jsonschema_description:"Description was preserved"`
}

type Examples struct {
StringExample string `json:"string_example" jsonschema:"example=hi,example=test"`
IntExample int `json:"int_example" jsonschema:"example=1,example=10,example=42"`
FloatExample float64 `json:"float_example" jsonschema:"example=2.0,example=3.14,example=13.37"`
IntArrayExample []int `json:"int_array_example" jsonschema:"example=1;2,example=3;4,example=5;6"`
MapExample map[string]string `json:"map_example" jsonschema:"example={\"key\": \"value\"}"`
MapArrayExample []map[string]string `json:"map_array_example" jsonschema:"example={\"a\": \"b\"};{\"c\": \"d\"},example={\"hello\": \"test\"}"`
}

func TestReflector(t *testing.T) {
r := new(Reflector)
s := "http://example.com/schema"
Expand Down Expand Up @@ -417,6 +426,7 @@ func TestSchemaGeneration(t *testing.T) {
}, "fixtures/keynamed.json"},
{MapType{}, &Reflector{}, "fixtures/map_type.json"},
{ArrayType{}, &Reflector{}, "fixtures/array_type.json"},
{Examples{}, &Reflector{}, "fixtures/examples.json"},
}

for _, tt := range tests {
Expand Down