github.com/grbit/go-json@v0.11.0/number_test.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package json_test
     6  
     7  import (
     8  	"regexp"
     9  	"testing"
    10  
    11  	"github.com/grbit/go-json"
    12  )
    13  
    14  func TestNumberIsValid(t *testing.T) {
    15  	// From: https://stackoverflow.com/a/13340826
    16  	var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
    17  
    18  	validTests := []string{
    19  		"0",
    20  		"-0",
    21  		"1",
    22  		"-1",
    23  		"0.1",
    24  		"-0.1",
    25  		"1234",
    26  		"-1234",
    27  		"12.34",
    28  		"-12.34",
    29  		"12E0",
    30  		"12E1",
    31  		"12e34",
    32  		"12E-0",
    33  		"12e+1",
    34  		"12e-34",
    35  		"-12E0",
    36  		"-12E1",
    37  		"-12e34",
    38  		"-12E-0",
    39  		"-12e+1",
    40  		"-12e-34",
    41  		"1.2E0",
    42  		"1.2E1",
    43  		"1.2e34",
    44  		"1.2E-0",
    45  		"1.2e+1",
    46  		"1.2e-34",
    47  		"-1.2E0",
    48  		"-1.2E1",
    49  		"-1.2e34",
    50  		"-1.2E-0",
    51  		"-1.2e+1",
    52  		"-1.2e-34",
    53  		"0E0",
    54  		"0E1",
    55  		"0e34",
    56  		"0E-0",
    57  		"0e+1",
    58  		"0e-34",
    59  		"-0E0",
    60  		"-0E1",
    61  		"-0e34",
    62  		"-0E-0",
    63  		"-0e+1",
    64  		"-0e-34",
    65  	}
    66  
    67  	for i, test := range validTests {
    68  		if !isValidNumber(test) {
    69  			t.Errorf("%d: %s should be valid", i, test)
    70  		}
    71  
    72  		var f float64
    73  		if err := json.Unmarshal([]byte(test), &f); err != nil {
    74  			t.Errorf("%d: %s should be valid but Unmarshal failed: %v", i, test, err)
    75  		}
    76  
    77  		if !jsonNumberRegexp.MatchString(test) {
    78  			t.Errorf("%d: %s should be valid but regexp does not match", i, test)
    79  		}
    80  	}
    81  
    82  	invalidTests := []string{
    83  		"",
    84  		"invalid",
    85  		"1.0.1",
    86  		"1..1",
    87  		"-1-2",
    88  		"012a42",
    89  		//"01.2",
    90  		//"012",
    91  		"12E12.12",
    92  		"1e2e3",
    93  		"1e+-2",
    94  		"1e--23",
    95  		"1e",
    96  		"e1",
    97  		"1e+",
    98  		"1ea",
    99  		"1a",
   100  		"1.a",
   101  		//"1.",
   102  		//"01",
   103  		//"1.e1",
   104  	}
   105  
   106  	for i, test := range invalidTests {
   107  		if isValidNumber(test) {
   108  			t.Errorf("%d: %s should be invalid", i, test)
   109  		}
   110  
   111  		var f float64
   112  		if err := json.Unmarshal([]byte(test), &f); err == nil {
   113  			t.Errorf("%d: %s should be invalid but unmarshal wrote %v", i, test, f)
   114  		}
   115  
   116  		if jsonNumberRegexp.MatchString(test) {
   117  			t.Errorf("%d: %s should be invalid but matches regexp", i, test)
   118  		}
   119  	}
   120  }
   121  
   122  // isValidNumber reports whether s is a valid JSON number literal.
   123  func isValidNumber(s string) bool {
   124  	// This function implements the JSON numbers grammar.
   125  	// See https://tools.ietf.org/html/rfc7159#section-6
   126  	// and https://www.json.org/img/number.png
   127  
   128  	if s == "" {
   129  		return false
   130  	}
   131  
   132  	// Optional -
   133  	if s[0] == '-' {
   134  		s = s[1:]
   135  		if s == "" {
   136  			return false
   137  		}
   138  	}
   139  
   140  	// Digits
   141  	switch {
   142  	default:
   143  		return false
   144  
   145  	case s[0] == '0':
   146  		s = s[1:]
   147  
   148  	case '1' <= s[0] && s[0] <= '9':
   149  		s = s[1:]
   150  		for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
   151  			s = s[1:]
   152  		}
   153  	}
   154  
   155  	// . followed by 1 or more digits.
   156  	if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' {
   157  		s = s[2:]
   158  		for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
   159  			s = s[1:]
   160  		}
   161  	}
   162  
   163  	// e or E followed by an optional - or + and
   164  	// 1 or more digits.
   165  	if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') {
   166  		s = s[1:]
   167  		if s[0] == '+' || s[0] == '-' {
   168  			s = s[1:]
   169  			if s == "" {
   170  				return false
   171  			}
   172  		}
   173  		for len(s) > 0 && '0' <= s[0] && s[0] <= '9' {
   174  			s = s[1:]
   175  		}
   176  	}
   177  
   178  	// Make sure we are at the end.
   179  	return s == ""
   180  }
   181  
   182  func BenchmarkNumberIsValid(b *testing.B) {
   183  	s := "-61657.61667E+61673"
   184  	for i := 0; i < b.N; i++ {
   185  		isValidNumber(s)
   186  	}
   187  }
   188  
   189  func BenchmarkNumberIsValidRegexp(b *testing.B) {
   190  	var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
   191  	s := "-61657.61667E+61673"
   192  	for i := 0; i < b.N; i++ {
   193  		jsonNumberRegexp.MatchString(s)
   194  	}
   195  }