From 32afd1a4e45e4ae42bb6bceb8a5823dfe8b44fa2 Mon Sep 17 00:00:00 2001 From: Joel Leitch Date: Wed, 10 Nov 2010 02:02:57 +0000 Subject: [PATCH] Fixing parsing of unquoted strings to be (somewhat) consistent with previous versions of Gson. The difference with this version is that Gson will throw a more specific exception rather than JsonParseException. --- gson/src/main/java/com/google/gson/Gson.java | 24 ++- .../main/java/com/google/gson/JsonParser.java | 33 +++- .../java/com/google/gson/JsonParserTest.java | 31 ++-- .../google/gson/functional/PrimitiveTest.java | 142 ++++++++++-------- 4 files changed, 149 insertions(+), 81 deletions(-) diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index ba11c628..cf2875d6 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -17,7 +17,10 @@ package com.google.gson; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; +import com.google.gson.stream.MalformedJsonException; + import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -452,7 +455,9 @@ public final class Gson { * @since 1.2 */ public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException, JsonIOException { - Object object = fromJson(new JsonReader(json), classOfT); + JsonReader jsonReader = new JsonReader(json); + Object object = fromJson(jsonReader, classOfT); + assertFullConsumption(object, jsonReader); return Primitives.wrap(classOfT).cast(object); } @@ -476,7 +481,22 @@ public final class Gson { * @since 1.2 */ public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { - return this.fromJson(new JsonReader(json), typeOfT); + JsonReader jsonReader = new JsonReader(json); + T object = fromJson(jsonReader, typeOfT); + assertFullConsumption(object, jsonReader); + return object; + } + + private static void assertFullConsumption(Object obj, JsonReader reader) { + try { + if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonIOException("JSON document was not fully consumed."); + } + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } } /** diff --git a/gson/src/main/java/com/google/gson/JsonParser.java b/gson/src/main/java/com/google/gson/JsonParser.java index 917c4118..507aff55 100755 --- a/gson/src/main/java/com/google/gson/JsonParser.java +++ b/gson/src/main/java/com/google/gson/JsonParser.java @@ -16,41 +16,58 @@ package com.google.gson; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.MalformedJsonException; + import java.io.EOFException; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; /** * A parser to parse Json into a parse tree of {@link JsonElement}s - * + * * @author Inderjeet Singh * @author Joel Leitch * @since 1.3 */ public final class JsonParser { - + /** * Parses the specified JSON string into a parse tree - * + * * @param json JSON text - * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON + * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON * @throws JsonParseException if the specified text is not valid JSON * @since 1.3 */ public JsonElement parse(String json) throws JsonSyntaxException { return parse(new StringReader(json)); } - + /** * Parses the specified JSON string into a parse tree - * + * * @param json JSON text - * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON + * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON * @throws JsonParseException if the specified text is not valid JSON * @since 1.3 */ public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException { - return parse(new JsonReader(json)); + try { + JsonReader jsonReader = new JsonReader(json); + JsonElement element = parse(jsonReader); + if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("Did not consume the entire document."); + } + return element; + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } catch (NumberFormatException e) { + throw new JsonSyntaxException(e); + } } /** diff --git a/gson/src/test/java/com/google/gson/JsonParserTest.java b/gson/src/test/java/com/google/gson/JsonParserTest.java index 2564ca8e..6d5fdd38 100644 --- a/gson/src/test/java/com/google/gson/JsonParserTest.java +++ b/gson/src/test/java/com/google/gson/JsonParserTest.java @@ -18,26 +18,27 @@ package com.google.gson; import com.google.gson.common.TestTypes.BagOfPrimitives; import com.google.gson.stream.JsonReader; + +import junit.framework.TestCase; + import java.io.CharArrayReader; import java.io.CharArrayWriter; import java.io.StringReader; -import junit.framework.TestCase; /** * Unit test for {@link JsonParser} - * + * * @author Inderjeet Singh */ public class JsonParserTest extends TestCase { - private JsonParser parser; - + @Override protected void setUp() throws Exception { super.setUp(); parser = new JsonParser(); } - + public void testParseString() { String json = "{a:10,b:'c'}"; JsonElement e = parser.parse(json); @@ -45,7 +46,7 @@ public class JsonParserTest extends TestCase { assertEquals(10, e.getAsJsonObject().get("a").getAsInt()); assertEquals("c", e.getAsJsonObject().get("b").getAsString()); } - + public void testParseEmptyString() { JsonElement e = parser.parse("\" \""); assertTrue(e.isJsonPrimitive()); @@ -56,12 +57,20 @@ public class JsonParserTest extends TestCase { JsonElement e = parser.parse(" "); assertTrue(e.isJsonNull()); } - + + public void testParseUnquotedStringSentence() { + String unquotedSentence = "Test is a test..blah blah"; + try { + parser.parse(unquotedSentence); + fail(); + } catch (JsonSyntaxException expected) { } + } + public void testParseMixedArray() { String json = "[{},13,\"stringValue\"]"; JsonElement e = parser.parse(json); assertTrue(e.isJsonArray()); - + JsonArray array = e.getAsJsonArray(); assertEquals("{}", array.get(0).toString()); assertEquals(13, array.get(1).getAsInt()); @@ -75,16 +84,16 @@ public class JsonParserTest extends TestCase { assertEquals(10, e.getAsJsonObject().get("a").getAsInt()); assertEquals("c", e.getAsJsonObject().get("b").getAsString()); } - + public void testReadWriteTwoObjects() throws Exception { - Gson gson= new Gson(); + Gson gson = new Gson(); CharArrayWriter writer= new CharArrayWriter(); BagOfPrimitives expectedOne = new BagOfPrimitives(1, 1, true, "one"); writer.write(gson.toJson(expectedOne).toCharArray()); BagOfPrimitives expectedTwo = new BagOfPrimitives(2, 2, false, "two"); writer.write(gson.toJson(expectedTwo).toCharArray()); CharArrayReader reader = new CharArrayReader(writer.toCharArray()); - + JsonReader parser = new JsonReader(reader); parser.setLenient(true); JsonElement element1 = Streams.parse(parser); diff --git a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java index 9b69bdb3..65972c12 100644 --- a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java +++ b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java @@ -16,15 +16,20 @@ package com.google.gson.functional; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import com.google.gson.LongSerializationPolicy; +import com.google.gson.common.TestTypes.CrazyLongTypeAdapter; + +import junit.framework.TestCase; + import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; -import com.google.gson.*; -import junit.framework.TestCase; - -import com.google.gson.common.TestTypes.CrazyLongTypeAdapter; - /** * Functional tests for Json primitive values: integers, and floating point numbers. * @@ -141,22 +146,22 @@ public class PrimitiveTest extends TestCase { value = gson.fromJson("[true]", boolean.class); assertEquals(true, value); } - + public void testNumberSerialization() { Number expected = 1L; String json = gson.toJson(expected); assertEquals(expected.toString(), json); - + json = gson.toJson(expected, Number.class); assertEquals(expected.toString(), json); } - + public void testNumberDeserialization() { String json = "1"; Number expected = new Integer(json); Number actual = gson.fromJson(json, Number.class); assertEquals(expected.intValue(), actual.intValue()); - + json = String.valueOf(Long.MAX_VALUE); expected = new Long(json); actual = gson.fromJson(json, Number.class); @@ -182,7 +187,7 @@ public class PrimitiveTest extends TestCase { assertEquals("[-122.08]", gson.toJson(target, double[].class)); assertEquals("[-122.08]", gson.toJson(target, Double[].class)); } - + public void testDoubleAsStringRepresentationDeserialization() { String doubleValue = "1.0043E+5"; Double expected = Double.valueOf(doubleValue); @@ -192,7 +197,7 @@ public class PrimitiveTest extends TestCase { double actual1 = gson.fromJson(doubleValue, double.class); assertEquals(expected.doubleValue(), actual1); } - + public void testDoubleNoFractAsStringRepresentationDeserialization() { String doubleValue = "1E+5"; Double expected = Double.valueOf(doubleValue); @@ -262,20 +267,20 @@ public class PrimitiveTest extends TestCase { BigDecimal actual = gson.fromJson("1.55", BigDecimal.class); assertEquals(expected, actual); } - + public void testBigDecimalPreservePrecisionSerialization() { String expectedValue = "1.000"; BigDecimal obj = new BigDecimal(expectedValue); String actualValue = gson.toJson(obj); - + assertEquals(expectedValue, actualValue); } - + public void testBigDecimalPreservePrecisionDeserialization() { String json = "1.000"; BigDecimal expected = new BigDecimal(json); BigDecimal actual = gson.fromJson(json, BigDecimal.class); - + assertEquals(expected, actual); } @@ -285,7 +290,7 @@ public class PrimitiveTest extends TestCase { BigDecimal actual = gson.fromJson(doubleValue, BigDecimal.class); assertEquals(expected, actual); } - + public void testBigDecimalNoFractAsStringRepresentationDeserialization() { String doubleValue = "5E+5"; BigDecimal expected = new BigDecimal(doubleValue); @@ -339,7 +344,7 @@ public class PrimitiveTest extends TestCase { fail("BigInteger can not be decimal values."); } catch (JsonParseException expected) { } } - + public void testOverridingDefaultPrimitiveSerialization() { CrazyLongTypeAdapter typeAdapter = new CrazyLongTypeAdapter(); gson = new GsonBuilder() @@ -349,7 +354,7 @@ public class PrimitiveTest extends TestCase { long value = 1L; String serializedValue = gson.toJson(value); assertEquals(String.valueOf(value + CrazyLongTypeAdapter.DIFFERENCE), serializedValue); - + long deserializedValue = gson.fromJson(serializedValue, long.class); assertEquals(value, deserializedValue); } @@ -357,54 +362,54 @@ public class PrimitiveTest extends TestCase { private String extractElementFromArray(String json) { return json.substring(json.indexOf('[') + 1, json.indexOf(']')); } - + public void testDoubleNaNSerializationNotSupportedByDefault() { try { double nan = Double.NaN; gson.toJson(nan); fail("Gson should not accept NaN for serialization"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } try { gson.toJson(Double.NaN); fail("Gson should not accept NaN for serialization"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } - + public void testDoubleNaNSerialization() { Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create(); double nan = Double.NaN; assertEquals("NaN", gson.toJson(nan)); assertEquals("NaN", gson.toJson(Double.NaN)); } - + public void testDoubleNaNDeserialization() { assertTrue(Double.isNaN(gson.fromJson("NaN", Double.class))); assertTrue(Double.isNaN(gson.fromJson("NaN", double.class))); } - + public void testFloatNaNSerializationNotSupportedByDefault() { try { float nan = Float.NaN; gson.toJson(nan); fail("Gson should not accept NaN for serialization"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } try { gson.toJson(Float.NaN); fail("Gson should not accept NaN for serialization"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } - + public void testFloatNaNSerialization() { Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create(); float nan = Float.NaN; assertEquals("NaN", gson.toJson(nan)); assertEquals("NaN", gson.toJson(Float.NaN)); } - + public void testFloatNaNDeserialization() { assertTrue(Float.isNaN(gson.fromJson("NaN", Float.class))); assertTrue(Float.isNaN(gson.fromJson("NaN", float.class))); @@ -414,7 +419,7 @@ public class PrimitiveTest extends TestCase { try { gson.fromJson("NaN", BigDecimal.class); fail("Gson should not accept NaN for deserialization by default."); - } catch (JsonParseException expected) { + } catch (JsonParseException expected) { } } @@ -423,131 +428,131 @@ public class PrimitiveTest extends TestCase { double infinity = Double.POSITIVE_INFINITY; gson.toJson(infinity); fail("Gson should not accept positive infinity for serialization by default."); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } try { gson.toJson(Double.POSITIVE_INFINITY); fail("Gson should not accept positive infinity for serialization by default."); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } - + public void testDoubleInfinitySerialization() { Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create(); double infinity = Double.POSITIVE_INFINITY; assertEquals("Infinity", gson.toJson(infinity)); assertEquals("Infinity", gson.toJson(Double.POSITIVE_INFINITY)); } - + public void testDoubleInfinityDeserialization() { assertTrue(Double.isInfinite(gson.fromJson("Infinity", Double.class))); assertTrue(Double.isInfinite(gson.fromJson("Infinity", double.class))); } - + public void testFloatInfinitySerializationNotSupportedByDefault() { try { float infinity = Float.POSITIVE_INFINITY; gson.toJson(infinity); fail("Gson should not accept positive infinity for serialization by default"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } try { gson.toJson(Float.POSITIVE_INFINITY); fail("Gson should not accept positive infinity for serialization by default"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } - + public void testFloatInfinitySerialization() { Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create(); float infinity = Float.POSITIVE_INFINITY; assertEquals("Infinity", gson.toJson(infinity)); assertEquals("Infinity", gson.toJson(Float.POSITIVE_INFINITY)); } - + public void testFloatInfinityDeserialization() { assertTrue(Float.isInfinite(gson.fromJson("Infinity", Float.class))); assertTrue(Float.isInfinite(gson.fromJson("Infinity", float.class))); } - + public void testBigDecimalInfinityDeserializationNotSupported() { try { gson.fromJson("Infinity", BigDecimal.class); fail("Gson should not accept positive infinity for deserialization with BigDecimal"); - } catch (JsonParseException expected) { + } catch (JsonParseException expected) { } } - + public void testNegativeInfinitySerializationNotSupportedByDefault() { try { double negativeInfinity = Double.NEGATIVE_INFINITY; gson.toJson(negativeInfinity); fail("Gson should not accept negative infinity for serialization by default"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } try { gson.toJson(Double.NEGATIVE_INFINITY); fail("Gson should not accept negative infinity for serialization by default"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } - + public void testNegativeInfinitySerialization() { Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create(); double negativeInfinity = Double.NEGATIVE_INFINITY; assertEquals("-Infinity", gson.toJson(negativeInfinity)); assertEquals("-Infinity", gson.toJson(Double.NEGATIVE_INFINITY)); } - + public void testNegativeInfinityDeserialization() { assertTrue(Double.isInfinite(gson.fromJson("-Infinity", double.class))); assertTrue(Double.isInfinite(gson.fromJson("-Infinity", Double.class))); } - + public void testNegativeInfinityFloatSerializationNotSupportedByDefault() { try { float negativeInfinity = Float.NEGATIVE_INFINITY; gson.toJson(negativeInfinity); fail("Gson should not accept negative infinity for serialization by default"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } try { gson.toJson(Float.NEGATIVE_INFINITY); fail("Gson should not accept negative infinity for serialization by default"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException expected) { } } - + public void testNegativeInfinityFloatSerialization() { Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create(); float negativeInfinity = Float.NEGATIVE_INFINITY; assertEquals("-Infinity", gson.toJson(negativeInfinity)); assertEquals("-Infinity", gson.toJson(Float.NEGATIVE_INFINITY)); } - + public void testNegativeInfinityFloatDeserialization() { assertTrue(Float.isInfinite(gson.fromJson("-Infinity", float.class))); assertTrue(Float.isInfinite(gson.fromJson("-Infinity", Float.class))); } - + public void testBigDecimalNegativeInfinityDeserializationNotSupported() { try { gson.fromJson("-Infinity", BigDecimal.class); fail("Gson should not accept positive infinity for deserialization"); - } catch (JsonParseException expected) { + } catch (JsonParseException expected) { } - } - + } + public void testLongAsStringSerialization() throws Exception { gson = new GsonBuilder().setLongSerializationPolicy(LongSerializationPolicy.STRING).create(); String result = gson.toJson(15L); assertEquals("\"15\"", result); - + // Test with an integer and ensure its still a number result = gson.toJson(2); assertEquals("2", result); } - + public void testLongAsStringDeserialization() throws Exception { long value = gson.fromJson("\"15\"", long.class); assertEquals(15, value); @@ -556,12 +561,29 @@ public class PrimitiveTest extends TestCase { value = gson.fromJson("\"25\"", long.class); assertEquals(25, value); } - + + public void testQuotedStringSerializationAndDeserialization() throws Exception { + String value = "String Blah Blah Blah...1, 2, 3"; + String serializedForm = gson.toJson(value); + assertEquals("\"" + value + "\"", serializedForm); + + String actual = gson.fromJson(serializedForm, String.class); + assertEquals(value, actual); + } + + public void testUnquotedStringDeserialization() throws Exception { + String value = "String Blah Blah Blah...1, 2, 3"; + try { + gson.fromJson(value, String.class); + fail(); + } catch (JsonSyntaxException expected) { } + } + public void testHtmlCharacterSerialization() throws Exception { String target = ""; String result = gson.toJson(target); assertFalse(result.equals('"' + target + '"')); - + gson = new GsonBuilder().disableHtmlEscaping().create(); result = gson.toJson(target); assertTrue(result.equals('"' + target + '"')); @@ -572,11 +594,11 @@ public class PrimitiveTest extends TestCase { ClassWithIntegerField target = gson.fromJson(json, ClassWithIntegerField.class); assertEquals(10, target.i.intValue()); } - + private static class ClassWithIntegerField { Integer i; } - + public void testPrimitiveClassLiteral() { assertEquals(1, gson.fromJson("1", int.class).intValue()); assertEquals(1, gson.fromJson(new StringReader("1"), int.class).intValue());