Repository: networknt/json-schema-validator (java)

Issue:
Support numerals the way JSON spec defines them
Currently [`TypeValidator`](https://github.com/networknt/json-schema-validator/blob/master/src/main/java/com/networknt/schema/TypeValidator.java#L65) has a loose mode (which is great) but [`isInteger`](https://github.com/networknt/json-schema-validator/blob/master/src/main/java/com/networknt/schema/TypeValidator.java#L101) and [`isNumeric`](https://github.com/networknt/json-schema-validator/blob/master/src/main/java/com/networknt/schema/TypeValidator.java#L125) do not fully comply with numeric literals defined by [JSON spec](http://json.org/). In particular the following values are not regarded as numbers by JSON (some of them are successfully validated by `TypeValidator`)

```
"01.1", "1.", ".1", "E1", "E+1", "E-1", ".E1", ".E+1", ".E-1", ".1E1", ".1E+1", ".1E-1", "+1"
```

also  the following values are all valid numbers as per spec

```
"1", "-1", "1.1", "-1.1", "0E+1", "0E-1", "0E1", "-0E+1", "-0E-1", "-0E1", "0.1E+1", "0.1E-1", "0.1E1", "-0.1E+1", "-0.1E-1", "-0.1E1", "10.1", "-10.1", "10E+1", "10E-1", "10E1", "-10E+1", "-10E-1", "-10E1", "10.1E+1", "10.1E-1", "10.1E1", "-10.1E+1", "-10.1E-1", "-10.1E1" 
```

**Note**
`+1`, `0001` is currently successfully validated by `TypeValidator` adhering to spec might introduce breaking change for some of existing users

Pull request:
Almost JSON-spec compliant validation of numeric values
Fixes #118 , yet leaves `+1` as a valid option

Patch:
diff --git a/README.md b/README.md
index c2955f4..fc8ab26 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,14 @@
+This is an implementation of the [JSON Schema Core Draft v4](http://json-schema.org/latest/json-schema-core.html) specification for OpenAPI 3.0. It uses the [Jackson](https://github.com/FasterXML/jackson) for json parsing.
+
+[Stack Overflow](https://stackoverflow.com/questions/tagged/light-4j) |
+[Google Group](https://groups.google.com/forum/#!forum/light-4j) |
+[Gitter Chat](https://gitter.im/networknt/light-rest-4j) |
+[Subreddit](https://www.reddit.com/r/lightapi/) |
+[Youtube Channel](https://www.youtube.com/channel/UCHCRMWJVXw8iB7zKxF55Byw) |
+[Documentation](https://doc.networknt.com/library/json-overlay/) |
+[Contribution Guide](https://doc.networknt.com/contribute/) |
+
 [![Build Status](https://travis-ci.org/networknt/json-schema-validator.svg?branch=master)](https://travis-ci.org/networknt/json-schema-validator)
-[![Join the chat at https://gitter.im/networknt/json-schema-validator](https://badges.gitter.im/networknt/json-schema-validator.svg)](https://gitter.im/networknt/json-schema-validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 
 # json-schema-validator
 
@@ -14,8 +23,6 @@ and RPC schema for [light-hybrid-4j](http://www.networknt.com/style/light-hybrid
 * [Quickstart](#quickstart)
 
 
-This project is an implementation of the [JSON Schema Core Draft v4](http://json-schema.org/latest/json-schema-core.html)
-specification. It uses the [Jackson](https://github.com/FasterXML/jackson) for json parsing.
 
 # Why to use this library?
 
diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java
index 64e2158..b8c1055 100644
--- a/src/main/java/com/networknt/schema/TypeValidator.java
+++ b/src/main/java/com/networknt/schema/TypeValidator.java
@@ -102,6 +102,9 @@ public class TypeValidator extends BaseJsonValidator implements JsonValidator {
         if (str == null || str.equals("")) {
             return false;
         }
+
+        // all code below could be replaced with
+        //return str.matrch("[-+]?(?:0|[1-9]\\d*)")
         int i = 0;
         if (str.charAt(0) == '-' || str.charAt(0) == '+') {
             if (str.length() == 1) {
@@ -126,31 +129,75 @@ public class TypeValidator extends BaseJsonValidator implements JsonValidator {
         if (str == null || str.equals("")) {
             return false;
         }
+
+        // all code below could be replaced with
+        //return str.matrch("[-+]?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?")
         int i = 0;
-        boolean hasDot = false;
-        if (str.charAt(0) == '-' || str.charAt(0) == '+') {
+        int len = str.length();
+
+        if (str.charAt(i) == MINUS || str.charAt(i) == PLUS) {
             if (str.length() == 1) {
                 return false;
             }
             i = 1;
         }
-        for (; i < str.length(); i++) {
-            char c = str.charAt(i);
-            if ((c < '0' || c > '9') && c != '.') {
+
+        char c = str.charAt(i++);
+
+        if (c == CHAR_0) {
+            // TODO: if leading zeros are supported (counter to JSON spec) handle it here
+            if (i < len){
+                c = str.charAt(i++);
+                if (c != DOT && c != CHAR_E && c != CHAR_e) {
+                    return false;
+                }
+            }
+        } else if (CHAR_1 <= c && c <= CHAR_9) {
+            while ( i < len && CHAR_0 <= c  && c <= CHAR_9 ) {
+                c = str.charAt(i++);
+            }
+        } else {
+            return false;
+        }
+
+        if (c == DOT) {
+            if (i >= len) {
+                return false;
+            }
+            c = str.charAt(i++);
+            while ( i < len && CHAR_0 <= c  && c <= CHAR_9 ) {
+                c = str.charAt(i++);
+            }
+        }
+
+        if (c == CHAR_E || c == CHAR_e) {
+            if (i >= len ) {
                 return false;
             }
-            if (c == '.') {
-                if (hasDot) {
+            c = str.charAt(i++);
+            if (c == PLUS || c == MINUS) {
+                if (i >= len) {
                     return false;
                 }
-                else {
-                    hasDot = true;
-                }
+                c = str.charAt(i++);
+            }
+            while ( i < len && CHAR_0 <= c  && c <= CHAR_9 ) {
+                c = str.charAt(i++);
             }
         }
-        return true;
+
+        return i >= len && (CHAR_0 <= c  && c <= CHAR_9);
     }
 
+    private static final char CHAR_0 = '0';
+    private static final char CHAR_1 = '1';
+    private static final char CHAR_9 = '9';
+    private static final char MINUS = '-';
+    private static final char PLUS = '+';
+    private static final char DOT = '.';
+    private static final char CHAR_E = 'E';
+    private static final char CHAR_e = 'e';
+
     /**
      * Check if the type of the JsonNode's value is number based on the
      * status of typeLoose flag.


Test patch:
diff --git a/src/test/java/com/networknt/schema/TypeValidatorTest.java b/src/test/java/com/networknt/schema/TypeValidatorTest.java
new file mode 100755
index 0000000..bcba5a7
--- /dev/null
+++ b/src/test/java/com/networknt/schema/TypeValidatorTest.java
@@ -0,0 +1,36 @@
+package com.networknt.schema;
+
+import org.junit.Test;
+
+import static com.networknt.schema.TypeValidator.isNumeric;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class TypeValidatorTest {
+    
+    private static final String[] validNumericValues  = {
+            "1", "-1", "1.1", "-1.1", "0E+1", "0E-1", "0E1", "-0E+1", "-0E-1", "-0E1", "0.1E+1", "0.1E-1", "0.1E1",
+            "-0.1E+1", "-0.1E-1", "-0.1E1", "10.1", "-10.1", "10E+1", "10E-1", "10E1", "-10E+1", "-10E-1", "-10E1",
+            "10.1E+1", "10.1E-1", "10.1E1", "-10.1E+1", "-10.1E-1", "-10.1E1", "1E+0", "1E-0", "1E0",
+            "1E00000000000000000000"
+    };
+    private static final String[] invalidNumericValues = {
+            "01.1", "1.", ".1", "0.1.1", "E1", "E+1", "E-1", ".E1", ".E+1", ".E-1", ".1E1", ".1E+1", ".1E-1", "1E-",
+            "1E+", "1E", "+", "-", "1a", "0.1a", "0E1a", "0E-1a", "1.0a", "1.0aE1"
+            //, "+0", "+1" // for backward compatibility, in violation of JSON spec
+    };
+    
+    @Test
+    public void testNumeicValues() {
+        for(String validValue : validNumericValues) {
+            assertTrue(validValue, isNumeric(validValue));
+        }
+    }
+
+    @Test
+    public void testNonNumeicValues() {
+        for(String invalidValue : invalidNumericValues) {
+            assertFalse(invalidValue, isNumeric(invalidValue));
+        }
+    }
+}