diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 666e5f8bd3..735916d72c 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -38,7 +38,6 @@ 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.EOFException; import java.io.IOException; import java.io.Reader; @@ -983,8 +982,7 @@ public T fromJson(String json, Type typeOfT) throws JsonSyntaxException { */ public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException, JsonIOException { JsonReader jsonReader = newJsonReader(json); - Object object = fromJson(jsonReader, classOfT); - assertFullConsumption(object, jsonReader); + Object object = fromJson(jsonReader, classOfT, true); return Primitives.wrap(classOfT).cast(object); } @@ -1013,49 +1011,29 @@ public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException @SuppressWarnings("unchecked") public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { JsonReader jsonReader = newJsonReader(json); - T object = (T) fromJson(jsonReader, typeOfT); - assertFullConsumption(object, jsonReader); + T object = (T) fromJson(jsonReader, typeOfT, true); return object; } - private static void assertFullConsumption(Object obj, JsonReader reader) { - try { - if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) { - throw new JsonSyntaxException("JSON document was not fully consumed."); - } - } catch (MalformedJsonException e) { - throw new JsonSyntaxException(e); - } catch (IOException e) { - throw new JsonIOException(e); - } - } - /** - * Reads the next JSON value from {@code reader} and convert it to an object - * of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF. - * Since Type is not parameterized by T, this method is type unsafe and should be used carefully. - * - *

Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has - * multiple top-level JSON elements, or if there is trailing data. - * - *

The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}, - * regardless of the lenient mode setting of the provided reader. The lenient mode setting - * of the reader is restored once this method returns. - * - * @throws JsonIOException if there was a problem writing to the Reader - * @throws JsonSyntaxException if json is not a valid representation for an object of type + * @param requireEndDocument whether there must not be any trailing data after + * the first read JSON element */ - @SuppressWarnings("unchecked") - public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException { + private T fromJson(JsonReader reader, Type typeOfT, boolean requireEndDocument) throws JsonIOException, JsonSyntaxException { boolean isEmpty = true; boolean oldLenient = reader.isLenient(); reader.setLenient(true); try { reader.peek(); isEmpty = false; + @SuppressWarnings("unchecked") // this is not actually safe TypeToken typeToken = (TypeToken) TypeToken.get(typeOfT); TypeAdapter typeAdapter = getAdapter(typeToken); T object = typeAdapter.read(reader); + + if (requireEndDocument && reader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("JSON document was not fully consumed."); + } return object; } catch (EOFException e) { /* @@ -1080,6 +1058,25 @@ public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J } } + /** + * Reads the next JSON value from {@code reader} and convert it to an object + * of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF. + * Since Type is not parameterized by T, this method is type unsafe and should be used carefully. + * + *

Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has + * multiple top-level JSON elements, or if there is trailing data. + * + *

The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}, + * regardless of the lenient mode setting of the provided reader. The lenient mode setting + * of the reader is restored once this method returns. + * + * @throws JsonIOException if there was a problem writing to the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type + */ + public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException { + return fromJson(reader, typeOfT, false); + } + /** * This method deserializes the Json read from the specified parse tree into an object of the * specified type. It is not suitable to use if the specified class is a generic type since it diff --git a/gson/src/main/java/com/google/gson/JsonParser.java b/gson/src/main/java/com/google/gson/JsonParser.java index d3508c1073..adb3ca0183 100644 --- a/gson/src/main/java/com/google/gson/JsonParser.java +++ b/gson/src/main/java/com/google/gson/JsonParser.java @@ -17,9 +17,6 @@ import com.google.gson.internal.Streams; import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.MalformedJsonException; -import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -46,7 +43,7 @@ public JsonParser() {} * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON * @throws JsonParseException if the specified text is not valid JSON */ - public static JsonElement parseString(String json) throws JsonSyntaxException { + public static JsonElement parseString(String json) throws JsonParseException { return parseReader(new StringReader(json)); } @@ -62,20 +59,25 @@ public static JsonElement parseString(String json) throws JsonSyntaxException { * @throws JsonParseException if there is an IOException or if the specified * text is not valid JSON */ - public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException { + public static JsonElement parseReader(Reader reader) throws JsonParseException { + JsonReader jsonReader = new JsonReader(reader); + return parseReader(jsonReader, true); + } + + private static JsonElement parseReader(JsonReader reader, boolean requiredEndDocument) + throws JsonParseException { + boolean lenient = reader.isLenient(); + reader.setLenient(true); try { - JsonReader jsonReader = new JsonReader(reader); - JsonElement element = parseReader(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); + return Streams.parse(reader, requiredEndDocument); } catch (NumberFormatException e) { throw new JsonSyntaxException(e); + } catch (StackOverflowError e) { + throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e); + } catch (OutOfMemoryError e) { + throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e); + } finally { + reader.setLenient(lenient); } } @@ -91,19 +93,8 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso * @throws JsonParseException if there is an IOException or if the specified * text is not valid JSON */ - public static JsonElement parseReader(JsonReader reader) - throws JsonIOException, JsonSyntaxException { - boolean lenient = reader.isLenient(); - reader.setLenient(true); - try { - return Streams.parse(reader); - } catch (StackOverflowError e) { - throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e); - } catch (OutOfMemoryError e) { - throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e); - } finally { - reader.setLenient(lenient); - } + public static JsonElement parseReader(JsonReader reader) throws JsonParseException { + return parseReader(reader, false); } /** @deprecated Use {@link JsonParser#parseString} */ diff --git a/gson/src/main/java/com/google/gson/JsonStreamParser.java b/gson/src/main/java/com/google/gson/JsonStreamParser.java index 27597da652..0ed9ed54c8 100644 --- a/gson/src/main/java/com/google/gson/JsonStreamParser.java +++ b/gson/src/main/java/com/google/gson/JsonStreamParser.java @@ -61,7 +61,7 @@ public final class JsonStreamParser implements Iterator { public JsonStreamParser(String json) { this(new StringReader(json)); } - + /** * @param reader The data stream containing JSON elements concatenated to each other. * @since 1.4 @@ -71,7 +71,7 @@ public JsonStreamParser(Reader reader) { parser.setLenient(true); lock = new Object(); } - + /** * Returns the next available {@link JsonElement} on the reader. Throws a * {@link NoSuchElementException} if no element is available. @@ -86,9 +86,9 @@ public JsonElement next() throws JsonParseException { if (!hasNext()) { throw new NoSuchElementException(); } - + try { - return Streams.parse(parser); + return Streams.parse(parser, false); } catch (StackOverflowError e) { throw new JsonParseException("Failed parsing JSON source to Json", e); } catch (OutOfMemoryError e) { diff --git a/gson/src/main/java/com/google/gson/internal/Streams.java b/gson/src/main/java/com/google/gson/internal/Streams.java index 0bb73aa18e..33baaa0364 100644 --- a/gson/src/main/java/com/google/gson/internal/Streams.java +++ b/gson/src/main/java/com/google/gson/internal/Streams.java @@ -23,6 +23,7 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.internal.bind.TypeAdapters; 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.EOFException; @@ -39,13 +40,22 @@ private Streams() { /** * Takes a reader in any state and returns the next value as a JsonElement. + * + * @param requireEndDocument whether there must not be any trailing data after + * the JsonElement */ - public static JsonElement parse(JsonReader reader) throws JsonParseException { + public static JsonElement parse(JsonReader reader, boolean requireEndDocument) throws JsonParseException { boolean isEmpty = true; try { reader.peek(); isEmpty = false; - return TypeAdapters.JSON_ELEMENT.read(reader); + JsonElement element = TypeAdapters.JSON_ELEMENT.read(reader); + + if (requireEndDocument && reader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("Did not consume the entire document."); + } + return element; + } catch (EOFException e) { /* * For compatibility with JSON 1.5 and earlier, we return a JsonNull for diff --git a/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java index 50f46b5aad..c1c0168c3d 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java @@ -62,7 +62,7 @@ public TreeTypeAdapter(JsonSerializer serializer, JsonDeserializer deseria if (deserializer == null) { return delegate().read(in); } - JsonElement value = Streams.parse(in); + JsonElement value = Streams.parse(in, false); if (value.isJsonNull()) { return null; } @@ -162,5 +162,5 @@ private final class GsonContextImpl implements JsonSerializationContext, JsonDes @Override public R deserialize(JsonElement json, Type typeOfT) throws JsonParseException { return (R) gson.fromJson(json, typeOfT); } - }; + } } diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index abb0de2113..21ffbb7526 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -16,11 +16,15 @@ package com.google.gson; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + import com.google.gson.internal.Excluder; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.google.gson.stream.MalformedJsonException; import java.io.IOException; +import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Field; @@ -29,14 +33,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import junit.framework.TestCase; +import org.junit.Test; /** * Unit tests for {@link Gson}. * * @author Ryan Harter */ -public final class GsonTest extends TestCase { +public final class GsonTest { private static final Excluder CUSTOM_EXCLUDER = Excluder.DEFAULT .excludeFieldsWithoutExposeAnnotation() @@ -51,6 +55,7 @@ public final class GsonTest extends TestCase { private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE; private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER; + @Test public void testOverridesDefaultExcluder() { Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, @@ -66,6 +71,7 @@ public void testOverridesDefaultExcluder() { assertEquals(false, gson.htmlSafe()); } + @Test public void testClonedTypeAdapterFactoryListsAreIndependent() { Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, @@ -89,6 +95,7 @@ private static final class TestTypeAdapter extends TypeAdapter { @Override public Object read(JsonReader in) throws IOException { return null; } } + @Test public void testNewJsonWriter_Default() throws IOException { StringWriter writer = new StringWriter(); JsonWriter jsonWriter = new Gson().newJsonWriter(writer); @@ -111,6 +118,7 @@ public void testNewJsonWriter_Default() throws IOException { assertEquals("{\"\\u003ctest2\":true}", writer.toString()); } + @Test public void testNewJsonWriter_Custom() throws IOException { StringWriter writer = new StringWriter(); JsonWriter jsonWriter = new GsonBuilder() @@ -135,6 +143,7 @@ public void testNewJsonWriter_Custom() throws IOException { assertEquals(")]}'\n{\n \"test\": null,\n \" registerSubtype(Class type) { return new TypeAdapter() { @Override public R read(JsonReader in) throws IOException { - JsonElement jsonElement = Streams.parse(in); + JsonElement jsonElement = Streams.parse(in, false); JsonElement labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); if (labelJsonElement == null) { throw new JsonParseException("cannot deserialize " + baseType diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java index ab802be1d7..87e013ee8a 100644 --- a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java +++ b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java @@ -315,7 +315,7 @@ public enum Factory { }, OBJECT_READER { @Override public JsonReader create(String data) { - JsonElement element = Streams.parse(new JsonReader(new StringReader(data))); + JsonElement element = Streams.parse(new JsonReader(new StringReader(data)), false); return new JsonTreeReader(element); } };