diff --git a/jsonschema/json.go b/jsonschema/json.go index 75e3b5173..858bbd962 100644 --- a/jsonschema/json.go +++ b/jsonschema/json.go @@ -174,8 +174,18 @@ func reflectSchemaObject(t reflect.Type, defs map[string]Definition) (*Definitio continue case jsonTag == "": jsonTag = field.Name - case strings.HasSuffix(jsonTag, ",omitempty"): - jsonTag = strings.TrimSuffix(jsonTag, ",omitempty") + // case strings.HasSuffix(jsonTag, ",omitempty"): + // jsonTag = strings.TrimSuffix(jsonTag, ",omitempty") + // required = false + // omitempty may not be the end of tag + case strings.Contains(jsonTag, ",omitempty"): + // remove ,omitempty + jsonTag = strings.ReplaceAll(jsonTag, ",omitempty", "") + required = false + // and also omitzero + case strings.Contains(jsonTag, ",omitzero"): + // remove ,omitzero + jsonTag = strings.ReplaceAll(jsonTag, ",omitzero", "") required = false } @@ -197,6 +207,11 @@ func reflectSchemaObject(t reflect.Type, defs map[string]Definition) (*Definitio item.Nullable = nullable } + if strings.HasSuffix(jsonTag, ",string") { + jsonTag = strings.TrimSuffix(jsonTag, ",string") + item.Type = String + } + properties[jsonTag] = *item if s := field.Tag.Get("required"); s != "" { diff --git a/jsonschema/json_test.go b/jsonschema/json_test.go index 34f5d88eb..64d28582b 100644 --- a/jsonschema/json_test.go +++ b/jsonschema/json_test.go @@ -213,6 +213,12 @@ func TestStructToSchema(t *testing.T) { SnakeCase string `json:"snake_case" required:"true" description:"SnakeCase"` } + type NewTagOmitzero struct { + ID uint64 `json:"id,omitzero,string"` + Name string `json:"name"` + Age int `json:"age,omitzero"` + } + tests := []struct { name string in any @@ -618,6 +624,28 @@ func TestStructToSchema(t *testing.T) { "additionalProperties": false } } +}`, + }, + { + name: "Test int to string json and omitzero", + in: NewTagOmitzero{}, + want: `{ + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "age": { + "type": "integer" + } + }, + "required": [ + "name" + ], + "additionalProperties": false }`, }, } diff --git a/jsonschema/validate.go b/jsonschema/validate.go index 1bd2f809c..8d9dbc7d9 100644 --- a/jsonschema/validate.go +++ b/jsonschema/validate.go @@ -102,13 +102,14 @@ func validateObject(schema Definition, data any, defs map[string]Definition) boo return false } for _, field := range schema.Required { - if _, exists := dataMap[field]; !exists { + if value, exists := dataMap[field]; !exists || value == nil { return false } } for key, valueSchema := range schema.Properties { value, exists := dataMap[key] - if exists && !Validate(valueSchema, value, WithDefs(defs)) { + // if value is required and not exists or nil, should return false before + if exists && value != nil && !Validate(valueSchema, value, WithDefs(defs)) { return false } else if !exists && contains(schema.Required, key) { return false diff --git a/jsonschema/validate_test.go b/jsonschema/validate_test.go index aefdf4069..2263da223 100644 --- a/jsonschema/validate_test.go +++ b/jsonschema/validate_test.go @@ -161,6 +161,55 @@ func Test_Validate(t *testing.T) { }, }, }}, false}, + { + "test not required nil filed", + args{ + data: map[string]any{ + "name": "John", + "age": 28, + "email": nil, + }, + schema: jsonschema.Definition{ + Type: jsonschema.Object, + Properties: map[string]jsonschema.Definition{ + "name": {Type: jsonschema.String}, + "age": {Type: jsonschema.Integer}, + "email": {Type: jsonschema.String}, + }, + Required: []string{"name", "age"}, + }, + }, + true, + }, + { + "test not required nil object", + args{ + data: map[string]any{ + "name": "John", + "age": 28, + "address": nil, + }, + schema: jsonschema.Definition{ + Type: jsonschema.Object, + Properties: map[string]jsonschema.Definition{ + "name": {Type: jsonschema.String}, + "age": {Type: jsonschema.Integer}, + "address": { + Type: jsonschema.Object, + Properties: map[string]jsonschema.Definition{ + "street": {Type: jsonschema.String}, + "city": {Type: jsonschema.String}, + "state": {Type: jsonschema.String}, + "zip": {Type: jsonschema.String}, + }, + Required: []string{"street", "city", "state", "zip"}, + }, + }, + Required: []string{"name", "age"}, + }, + }, + true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {