Commit c4135dac authored by Russ Cox's avatar Russ Cox

encoding/json: streamline, unexport valid Number checking

Followup to CL 12250.

For #10281.

Change-Id: If25d9cac92f10327bb355f2d11b00c625b464661
Reviewed-on: https://go-review.googlesource.com/17199Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent e8cc083e
...@@ -60,7 +60,7 @@ import ( ...@@ -60,7 +60,7 @@ import (
// If the JSON array is smaller than the Go array, // If the JSON array is smaller than the Go array,
// the additional Go array elements are set to zero values. // the additional Go array elements are set to zero values.
// //
// To unmarshal a JSON object into a string-keyed map, Unmarshal first // To unmarshal a JSON object into a string-keyed map, Unmarshal first
// establishes a map to use, If the map is nil, Unmarshal allocates a new map. // establishes a map to use, If the map is nil, Unmarshal allocates a new map.
// Otherwise Unmarshal reuses the existing map, keeping existing entries. // Otherwise Unmarshal reuses the existing map, keeping existing entries.
// Unmarshal then stores key-value pairs from the JSON object into the map. // Unmarshal then stores key-value pairs from the JSON object into the map.
...@@ -184,122 +184,64 @@ func (n Number) Int64() (int64, error) { ...@@ -184,122 +184,64 @@ func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64) return strconv.ParseInt(string(n), 10, 64)
} }
// IsValid returns if the number is a valid JSON number literal. // isValidNumber reports whether s is a valid JSON number literal.
func (n Number) IsValid() bool { func isValidNumber(s string) bool {
// This function implements the JSON numbers grammar. // This function implements the JSON numbers grammar.
// See https://tools.ietf.org/html/rfc7159#section-6 // See https://tools.ietf.org/html/rfc7159#section-6
// and http://json.org/number.gif // and http://json.org/number.gif
l := len(n) if s == "" {
if l == 0 {
return false return false
} }
i := 0
c := n[i]
i++
// Optional - // Optional -
if c == '-' { if s[0] == '-' {
if i == l { s = s[1:]
if s == "" {
return false return false
} }
c = n[i]
i++
} }
// 1-9 // Digits
if c >= '1' && c <= '9' { switch {
// Eat digits. default:
for ; i < l; i++ {
c = n[i]
if c < '0' || c > '9' {
break
}
}
i++
} else if c != '0' {
// If it's not 0 or 1-9 it's invalid.
return false return false
} else {
if i == l {
// Just 0
return true
}
// Skip the 0 case s[0] == '0':
c = n[i] s = s[1:]
i++
}
// . followed by 1 or more digits.
if c == '.' {
if i == l {
// Just 1. is invalid.
return false
}
// . needs to be followed by at least one digit. case '1' <= s[0] && s[0] <= '9':
c = n[i] s = s[1:]
i++ for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
if c < '0' || c > '9' { s = s[1:]
return false
} }
}
// Eat digits. // . followed by 1 or more digits.
for ; i < l; i++ { if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' {
c = n[i] s = s[2:]
if c < '0' || c > '9' { for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
break s = s[1:]
}
} }
i++
} }
// e or E followed by an optional - or + and // e or E followed by an optional - or + and
// 1 or more digits. // 1 or more digits.
if c == 'e' || c == 'E' { if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') {
if i == l { s = s[1:]
// Just 1e is invalid. if s[0] == '+' || s[0] == '-' {
return false s = s[1:]
} if s == "" {
c = n[i]
i++
// Optional - or +
if c == '-' || c == '+' {
if i == l {
// Just 1e+ is invalid.
return false return false
} }
c = n[i]
i++
} }
for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
// Need to have a digit. s = s[1:]
if c < '0' || c > '9' {
return false
}
// Eat digits.
for ; i < l; i++ {
c = n[i]
if c < '0' || c > '9' {
break
}
} }
i++
} }
// Make sure we are at the end. // Make sure we are at the end.
if i <= l { return s == ""
return false
}
return true
} }
// decodeState represents the state while decoding a JSON value. // decodeState represents the state while decoding a JSON value.
...@@ -909,7 +851,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool ...@@ -909,7 +851,7 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool
default: default:
if v.Kind() == reflect.String && v.Type() == numberType { if v.Kind() == reflect.String && v.Type() == numberType {
v.SetString(s) v.SetString(s)
if !Number(s).IsValid() { if !isValidNumber(s) {
d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item)) d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item))
} }
break break
......
...@@ -534,8 +534,9 @@ func stringEncoder(e *encodeState, v reflect.Value, quoted bool) { ...@@ -534,8 +534,9 @@ func stringEncoder(e *encodeState, v reflect.Value, quoted bool) {
// we keep compatibility so check validity after this. // we keep compatibility so check validity after this.
if numStr == "" { if numStr == "" {
numStr = "0" // Number's zero-val numStr = "0" // Number's zero-val
} else if !Number(numStr).IsValid() { }
e.error(fmt.Errorf("json: invalid number literal, trying to marshal %s", v.String())) if !isValidNumber(numStr) {
e.error(fmt.Errorf("json: invalid number literal %q", numStr))
} }
e.WriteString(numStr) e.WriteString(numStr)
return return
......
...@@ -63,17 +63,17 @@ func TestNumberIsValid(t *testing.T) { ...@@ -63,17 +63,17 @@ func TestNumberIsValid(t *testing.T) {
} }
for _, test := range validTests { for _, test := range validTests {
if !Number(test).IsValid() { if !isValidNumber(test) {
t.Errorf("%s should be valid", test) t.Errorf("%s should be valid", test)
} }
var f float64 var f float64
if err := Unmarshal([]byte(test), &f); err != nil { if err := Unmarshal([]byte(test), &f); err != nil {
t.Errorf("%s should be invalid: %v", test, err) t.Errorf("%s should be valid but Unmarshal failed: %v", test, err)
} }
if !jsonNumberRegexp.MatchString(test) { if !jsonNumberRegexp.MatchString(test) {
t.Errorf("%s should be invalid", test) t.Errorf("%s should be valid but regexp does not match", test)
} }
} }
...@@ -102,32 +102,32 @@ func TestNumberIsValid(t *testing.T) { ...@@ -102,32 +102,32 @@ func TestNumberIsValid(t *testing.T) {
} }
for _, test := range invalidTests { for _, test := range invalidTests {
if Number(test).IsValid() { if isValidNumber(test) {
t.Errorf("%s should be invalid", test) t.Errorf("%s should be invalid", test)
} }
var f float64 var f float64
if err := Unmarshal([]byte(test), &f); err == nil { if err := Unmarshal([]byte(test), &f); err == nil {
t.Errorf("%s should be valid: %v", test, f) t.Errorf("%s should be invalid but unmarshal wrote %v", test, f)
} }
if jsonNumberRegexp.MatchString(test) { if jsonNumberRegexp.MatchString(test) {
t.Errorf("%s should be valid", test) t.Errorf("%s should be invalid but matches regexp", test)
} }
} }
} }
func BenchmarkNumberIsValid(b *testing.B) { func BenchmarkNumberIsValid(b *testing.B) {
n := Number("-61657.61667E+61673") s := "-61657.61667E+61673"
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
n.IsValid() isValidNumber(s)
} }
} }
func BenchmarkNumberIsValidRegexp(b *testing.B) { func BenchmarkNumberIsValidRegexp(b *testing.B) {
var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
n := "-61657.61667E+61673" s := "-61657.61667E+61673"
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
jsonNumberRegexp.MatchString(n) jsonNumberRegexp.MatchString(s)
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment