Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add example support for floats and arrays #31

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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) {
arvidfm marked this conversation as resolved.
Show resolved Hide resolved
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":
arvidfm marked this conversation as resolved.
Show resolved Hide resolved
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