From 2f2cd4dfc5f3dfcee30e5d8fae54ff2954e7b107 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Wed, 17 Aug 2016 12:52:04 -0400 Subject: [PATCH] Fix for number output bug. java.lang.Number is currently output without any validation. For all java.* Numbers, this is fine, but for custom Number implementations like Complex or Fraction, the resulting JSON output may be invalid. For example: If a Fraction implementation defines its' toString method as `return numerator + "/" + denominator`, then the resulting JSON output would be something like this: ```json { "fraction" : 1/2 } ``` This is not valid JSON. This commit verifies that the string representation of the number is close to a JSON formatted number by use of the BigDecimal constructor. If the constructor throws a NumberFormatException, then the string value is instead quoted as a string. The example above would instead output like the following: ```json { "fraction" : "1/2" } ``` --- JSONObject.java | 51 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index ed86fab..15e74b8 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -838,7 +838,7 @@ public class JSONObject { } testValidity(number); -// Shave off trailing zeros and decimal point, if possible. + // Shave off trailing zeros and decimal point, if possible. String string = number.toString(); if (string.indexOf('.') > 0 && string.indexOf('e') < 0 @@ -1693,7 +1693,18 @@ public class JSONObject { throw new JSONException("Bad value from toJSONString: " + object); } if (value instanceof Number) { - return numberToString((Number) value); + // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex + final String numberAsString = numberToString((Number) value); + try { + // Use the BigDecimal constructor for it's parser to validate the format. + new BigDecimal(numberAsString); + // Close enough to a JSON number that we will return it unquoted + return numberAsString; + } catch (NumberFormatException ex){ + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + return quote(numberAsString); + } } if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) { @@ -1783,6 +1794,30 @@ public class JSONObject { int indentFactor, int indent) throws JSONException, IOException { if (value == null || value.equals(null)) { writer.write("null"); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else if (value instanceof Number) { + // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary + final String numberAsString = numberToString((Number) value); + try { + // Use the BigDecimal constructor for it's parser to validate the format. + @SuppressWarnings("unused") + BigDecimal testNum = new BigDecimal(numberAsString); + // Close enough to a JSON number that we will use it unquoted + writer.write(numberAsString); + } catch (NumberFormatException ex){ + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + quote(numberAsString, writer); + } + } else if (value instanceof Boolean) { + writer.write(value.toString()); } else if (value instanceof JSONObject) { ((JSONObject) value).write(writer, indentFactor, indent); } else if (value instanceof JSONArray) { @@ -1795,18 +1830,6 @@ public class JSONObject { new JSONArray(coll).write(writer, indentFactor, indent); } else if (value.getClass().isArray()) { new JSONArray(value).write(writer, indentFactor, indent); - } else if (value instanceof Number) { - writer.write(numberToString((Number) value)); - } else if (value instanceof Boolean) { - writer.write(value.toString()); - } else if (value instanceof JSONString) { - Object o; - try { - o = ((JSONString) value).toJSONString(); - } catch (Exception e) { - throw new JSONException(e); - } - writer.write(o != null ? o.toString() : quote(value.toString())); } else { quote(value.toString(), writer); }