diff --git a/baked_in.go b/baked_in.go index 13ec143..deb00a2 100644 --- a/baked_in.go +++ b/baked_in.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "encoding/json" "fmt" "net" "net/url" @@ -166,6 +167,7 @@ var ( "html_encoded": isHTMLEncoded, "url_encoded": isURLEncoded, "dir": isDir, + "json": isJSON, "hostname_port": isHostnamePort, "lowercase": isLowercase, "uppercase": isUppercase, @@ -2011,6 +2013,17 @@ func isDir(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %T", field.Interface())) } +// isJSON is the validation function for validating if the current field's value is a valid json string. +func isJSON(fl FieldLevel) bool { + field := fl.Field() + + if field.Kind() == reflect.String { + val := field.String() + return json.Valid([]byte(val)) + } + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + // isHostnamePort validates a : combination for fields typically used for socket address. func isHostnamePort(fl FieldLevel) bool { val := fl.Field().String() diff --git a/doc.go b/doc.go index 3f304f6..9bcba9a 100644 --- a/doc.go +++ b/doc.go @@ -685,6 +685,12 @@ does any email provider accept all possibilities. Usage: email +JSON String + +This validates that a string value is valid JSON + + Usage: json + File path This validates that a string value contains a valid file path and that diff --git a/translations/en/en.go b/translations/en/en.go index cf89fd2..be61d85 100644 --- a/translations/en/en.go +++ b/translations/en/en.go @@ -1322,6 +1322,11 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er }, }, { + tag: "json", + translation: "{0} must be a valid json string", + override: false, + }, + { tag: "lowercase", translation: "{0} must be a lowercase string", override: false, diff --git a/translations/en/en_test.go b/translations/en/en_test.go index 769409a..b95ca98 100644 --- a/translations/en/en_test.go +++ b/translations/en/en_test.go @@ -141,6 +141,7 @@ func TestTranslations(t *testing.T) { UniqueSlice []string `validate:"unique"` UniqueArray [3]string `validate:"unique"` UniqueMap map[string]string `validate:"unique"` + JSONString string `validate:"json"` LowercaseString string `validate:"lowercase"` UppercaseString string `validate:"uppercase"` } @@ -637,6 +638,10 @@ func TestTranslations(t *testing.T) { ns: "Test.UniqueMap", expected: "UniqueMap must contain unique values", }, + { + ns: "Test.JSONString", + expected: "JSONString must be a valid json string", + }, { ns: "Test.LowercaseString", expected: "LowercaseString must be a lowercase string", diff --git a/validator_test.go b/validator_test.go index 7c831ed..1323867 100644 --- a/validator_test.go +++ b/validator_test.go @@ -9004,6 +9004,54 @@ func TestGetTag(t *testing.T) { Equal(t, tag, "mytag") } +func TestJSONValidation(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {`foo`, false}, + {`}{`, false}, + {`{]`, false}, + {`{}`, true}, + {`{"foo":"bar"}`, true}, + {`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, + {`{"foo": 3 "bar": 4}`, false}, + {`{"foo": 3 ,"bar": 4`, false}, + {`{foo": 3, "bar": 4}`, false}, + {`foo`, false}, + {`1`, true}, + {`true`, true}, + {`null`, true}, + {`"null"`, true}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.param, "json") + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d json failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d json failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "json" { + t.Fatalf("Index: %d json failed Error: %s", i, errs) + } + } + } + } + + PanicMatches(t, func() { + _ = validate.Var(2, "json") + }, "Bad field type int") +} + func Test_hostnameport_validator(t *testing.T) { type Host struct { @@ -9107,4 +9155,4 @@ func TestUppercaseValidation(t *testing.T) { PanicMatches(t, func() { _ = validate.Var(2, "uppercase") }, "Bad field type int") -} +} \ No newline at end of file