// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package json_test import ( "regexp" "testing" "github.com/goccy/go-json" ) func TestNumberIsValid(t *testing.T) { // From: https://stackoverflow.com/a/13340826 var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) validTests := []string{ "0", "-0", "1", "-1", "0.1", "-0.1", "1234", "-1234", "12.34", "-12.34", "12E0", "12E1", "12e34", "12E-0", "12e+1", "12e-34", "-12E0", "-12E1", "-12e34", "-12E-0", "-12e+1", "-12e-34", "1.2E0", "1.2E1", "1.2e34", "1.2E-0", "1.2e+1", "1.2e-34", "-1.2E0", "-1.2E1", "-1.2e34", "-1.2E-0", "-1.2e+1", "-1.2e-34", "0E0", "0E1", "0e34", "0E-0", "0e+1", "0e-34", "-0E0", "-0E1", "-0e34", "-0E-0", "-0e+1", "-0e-34", } for i, test := range validTests { if !isValidNumber(test) { t.Errorf("%d: %s should be valid", i, test) } var f float64 if err := json.Unmarshal([]byte(test), &f); err != nil { t.Errorf("%d: %s should be valid but Unmarshal failed: %v", i, test, err) } if !jsonNumberRegexp.MatchString(test) { t.Errorf("%d: %s should be valid but regexp does not match", i, test) } } invalidTests := []string{ "", "invalid", "1.0.1", "1..1", "-1-2", "012a42", //"01.2", //"012", "12E12.12", "1e2e3", "1e+-2", "1e--23", "1e", "e1", "1e+", "1ea", "1a", "1.a", //"1.", //"01", //"1.e1", } for i, test := range invalidTests { if isValidNumber(test) { t.Errorf("%d: %s should be invalid", i, test) } var f float64 if err := json.Unmarshal([]byte(test), &f); err == nil { t.Errorf("%d: %s should be invalid but unmarshal wrote %v", i, test, f) } if jsonNumberRegexp.MatchString(test) { t.Errorf("%d: %s should be invalid but matches regexp", i, test) } } } // isValidNumber reports whether s is a valid JSON number literal. func isValidNumber(s string) bool { // This function implements the JSON numbers grammar. // See https://tools.ietf.org/html/rfc7159#section-6 // and https://www.json.org/img/number.png if s == "" { return false } // Optional - if s[0] == '-' { s = s[1:] if s == "" { return false } } // Digits switch { default: return false case s[0] == '0': s = s[1:] case '1' <= s[0] && s[0] <= '9': s = s[1:] for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { s = s[1:] } } // . followed by 1 or more digits. if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { s = s[2:] for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { s = s[1:] } } // e or E followed by an optional - or + and // 1 or more digits. if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { s = s[1:] if s[0] == '+' || s[0] == '-' { s = s[1:] if s == "" { return false } } for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { s = s[1:] } } // Make sure we are at the end. return s == "" } func BenchmarkNumberIsValid(b *testing.B) { s := "-61657.61667E+61673" for i := 0; i < b.N; i++ { isValidNumber(s) } } func BenchmarkNumberIsValidRegexp(b *testing.B) { var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) s := "-61657.61667E+61673" for i := 0; i < b.N; i++ { jsonNumberRegexp.MatchString(s) } }