diff --git a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java index 87b522f0c4..71113b62cf 100644 --- a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java +++ b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -178,6 +178,8 @@ public static RuntimeTypeAdapterFactory of(Class baseType) { /** * Ensures that this factory will handle not just the given {@code baseType}, but any subtype * of that type. + * + * @return this */ public RuntimeTypeAdapterFactory recognizeSubtypes() { this.recognizeSubtypes = true; @@ -190,6 +192,7 @@ public RuntimeTypeAdapterFactory recognizeSubtypes() { * * @throws IllegalArgumentException if either {@code type} or {@code label} * have already been registered on this type adapter. + * @return this */ public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { if (type == null || label == null) { @@ -209,6 +212,7 @@ public RuntimeTypeAdapterFactory registerSubtype(Class type, Str * * @throws IllegalArgumentException if either {@code type} or its simple name * have already been registered on this type adapter. + * @return this */ public RuntimeTypeAdapterFactory registerSubtype(Class type) { return registerSubtype(type, type.getSimpleName()); diff --git a/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java b/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java index 5159001c97..e99b9ca9c7 100644 --- a/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java +++ b/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java @@ -19,7 +19,10 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; import junit.framework.TestCase; public final class RuntimeTypeAdapterFactoryTest extends TestCase { @@ -191,10 +194,10 @@ public void testSerializeCollidingTypeFieldName() { public void testSerializeWrappedNullValue() { TypeAdapterFactory billingAdapter = RuntimeTypeAdapterFactory.of(BillingInstrument.class) .registerSubtype(CreditCard.class) - .registerSubtype(BankTransfer.class); + .registerSubtype(BankTransfer.class); Gson gson = new GsonBuilder() .registerTypeAdapterFactory(billingAdapter) - .create(); + .create(); String serialized = gson.toJson(new BillingInstrumentWrapper(null), BillingInstrumentWrapper.class); BillingInstrumentWrapper deserialized = gson.fromJson(serialized, BillingInstrumentWrapper.class); assertNull(deserialized.instrument); @@ -229,4 +232,51 @@ static class BankTransfer extends BillingInstrument { this.bankAccount = bankAccount; } } + + public void testJsonAdapterDelegate() throws Exception { + Gson gson = new Gson(); + Shape shape = new Circle(25); + String json = gson.toJson(shape); + assertEquals("{\"radius\":25,\"type\":\"CIRCLE\"}", json); + shape = gson.fromJson(json, Shape.class); + assertEquals(25, ((Circle)shape).radius); + + shape = new Square(15); + json = gson.toJson(shape); + assertEquals("{\"side\":15,\"type\":\"SQUARE\"}", json); + shape = gson.fromJson(json, Shape.class); + assertEquals(15, ((Square)shape).side); + assertEquals(ShapeType.SQUARE, shape.type); + } + + @JsonAdapter(Shape.JsonAdapterFactory.class) + static class Shape { + final ShapeType type; + Shape(ShapeType type) { this.type = type; } + + private static final class JsonAdapterFactory implements TypeAdapterFactory { + private static final RuntimeTypeAdapterFactory delegate = RuntimeTypeAdapterFactory.of(Shape.class, "type", true) + .registerSubtype(Circle.class, ShapeType.CIRCLE.toString()) + .registerSubtype(Square.class, ShapeType.SQUARE.toString()); + + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + return delegate.create(gson, type); + } + } + } + + enum ShapeType { + SQUARE, CIRCLE + } + + private static final class Circle extends Shape { + final int radius; + Circle(int radius) { super(ShapeType.CIRCLE); this.radius = radius; } + } + + private static final class Square extends Shape { + final int side; + Square(int side) { super(ShapeType.SQUARE); this.side = side; } + } } diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 22071a17d8..59215478b5 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -39,7 +39,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; @@ -1119,23 +1118,10 @@ public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyn */ public T fromJson(Reader json, TypeToken typeOfT) throws JsonIOException, JsonSyntaxException { JsonReader jsonReader = newJsonReader(json); - T object = fromJson(jsonReader, typeOfT); - assertFullConsumption(object, jsonReader); + T object = 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); - } - } - // fromJson(JsonReader, Class) is unfortunately missing and cannot be added now without breaking // source compatibility in certain cases, see https://github.com/google/gson/pull/1700#discussion_r973764414 @@ -1201,6 +1187,14 @@ public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J * @see #fromJson(JsonReader, Type) */ public T fromJson(JsonReader reader, TypeToken typeOfT) throws JsonIOException, JsonSyntaxException { + return fromJson(reader, typeOfT, false); + } + + /** + * @param requireEndDocument whether there must not be any trailing data after + * the first read JSON element + */ + private T fromJson(JsonReader reader, TypeToken typeOfT, boolean requireEndDocument) throws JsonIOException, JsonSyntaxException { boolean isEmpty = true; boolean oldLenient = reader.isLenient(); reader.setLenient(true); @@ -1209,6 +1203,10 @@ public T fromJson(JsonReader reader, TypeToken typeOfT) throws JsonIOExce isEmpty = false; TypeAdapter typeAdapter = getAdapter(typeOfT); 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) { /* @@ -1313,7 +1311,7 @@ public T fromJson(JsonElement json, TypeToken typeOfT) throws JsonSyntaxE if (json == null) { return null; } - return fromJson(new JsonTreeReader(json), typeOfT); + return fromJson(new JsonTreeReader(json), typeOfT, false); } static class FutureTypeAdapter extends SerializationDelegatingTypeAdapter { diff --git a/gson/src/main/java/com/google/gson/JsonParser.java b/gson/src/main/java/com/google/gson/JsonParser.java index 5b80042039..f6dfca091c 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; @@ -47,7 +44,7 @@ public JsonParser() {} * @throws JsonParseException if the specified text is not valid JSON * @since 2.8.6 */ - public static JsonElement parseString(String json) throws JsonSyntaxException { + public static JsonElement parseString(String json) throws JsonParseException { return parseReader(new StringReader(json)); } @@ -64,20 +61,25 @@ public static JsonElement parseString(String json) throws JsonSyntaxException { * text is not valid JSON * @since 2.8.6 */ - 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); } } @@ -94,19 +96,8 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso * text is not valid JSON * @since 2.8.6 */ - 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 eafbbbec4d..20b3cdb0ad 100644 --- a/gson/src/main/java/com/google/gson/internal/Streams.java +++ b/gson/src/main/java/com/google/gson/internal/Streams.java @@ -20,9 +20,11 @@ import com.google.gson.JsonIOException; import com.google.gson.JsonNull; import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; 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; @@ -38,15 +40,40 @@ private Streams() { throw new UnsupportedOperationException(); } + /** + * @deprecated + * This method is declared in an internal Gson class. Use the public API class {@link JsonParser} + * (note that {@code JsonParser} parses JSON in lenient mode), or obtain the {@code TypeAdapter} + * for {@code JsonElement} and use that for parsing: + *
{@code
+   *    TypeAdapter adapter = gson.getAdapter(JsonElement.class);
+   *    JsonElement element = adapter.read(...);
+   *    }
+ */ + // Only keeping this internal method because third-party projects depend on it + @Deprecated + public static JsonElement parse(JsonReader reader) { + return parse(reader, false); + } + /** * 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 560234c07c..22616696a7 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 @@ -69,7 +69,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 (nullSafe && value.isJsonNull()) { return null; } diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index 4274d26a55..c949055743 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -16,12 +16,18 @@ package com.google.gson; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import com.google.gson.internal.Excluder; import com.google.gson.reflect.TypeToken; 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; @@ -32,14 +38,14 @@ import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -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() @@ -54,6 +60,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, @@ -69,6 +76,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, @@ -92,6 +100,7 @@ private static final class TestTypeAdapter extends TypeAdapter { @Override public Object read(JsonReader in) throws IOException { return null; } } + @Test public void testGetAdapter_Null() { Gson gson = new Gson(); try { @@ -102,6 +111,7 @@ public void testGetAdapter_Null() { } } + @Test public void testGetAdapter_Concurrency() { class DummyAdapter extends TypeAdapter { @Override public void write(JsonWriter out, T value) throws IOException { @@ -155,6 +165,7 @@ class DummyAdapter extends TypeAdapter { assertSame(threadAdapter.get(), adapter); } + @Test public void testNewJsonWriter_Default() throws IOException { StringWriter writer = new StringWriter(); JsonWriter jsonWriter = new Gson().newJsonWriter(writer); @@ -177,6 +188,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() @@ -201,6 +213,7 @@ public void testNewJsonWriter_Custom() throws IOException { assertEquals(")]}'\n{\n \"test\": null,\n \"() { diff --git a/gson/src/test/java/com/google/gson/JsonParserTest.java b/gson/src/test/java/com/google/gson/JsonParserTest.java index a05aa32296..7ca8f508a2 100644 --- a/gson/src/test/java/com/google/gson/JsonParserTest.java +++ b/gson/src/test/java/com/google/gson/JsonParserTest.java @@ -16,23 +16,27 @@ package com.google.gson; -import java.io.CharArrayReader; -import java.io.CharArrayWriter; -import java.io.IOException; -import java.io.StringReader; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.google.gson.common.TestTypes.BagOfPrimitives; import com.google.gson.internal.Streams; import com.google.gson.stream.JsonReader; +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import org.junit.Test; /** * Unit test for {@link JsonParser} * * @author Inderjeet Singh */ -public class JsonParserTest extends TestCase { - +public class JsonParserTest { + @Test public void testParseInvalidJson() { try { JsonParser.parseString("[[]"); @@ -40,6 +44,7 @@ public void testParseInvalidJson() { } catch (JsonSyntaxException expected) { } } + @Test public void testParseUnquotedStringArrayFails() { JsonElement element = JsonParser.parseString("[a,b,c]"); assertEquals("a", element.getAsJsonArray().get(0).getAsString()); @@ -48,6 +53,7 @@ public void testParseUnquotedStringArrayFails() { assertEquals(3, element.getAsJsonArray().size()); } + @Test public void testParseString() { String json = "{a:10,b:'c'}"; JsonElement e = JsonParser.parseString(json); @@ -56,21 +62,36 @@ public void testParseString() { assertEquals("c", e.getAsJsonObject().get("b").getAsString()); } + @Test + public void testParseStringMultipleTopLevel() { + String json = "{}1"; + try { + JsonParser.parseString(json); + fail(); + } catch (JsonSyntaxException expected) { + assertEquals("Did not consume the entire document.", expected.getMessage()); + } + } + + @Test public void testParseEmptyString() { JsonElement e = JsonParser.parseString("\" \""); assertTrue(e.isJsonPrimitive()); assertEquals(" ", e.getAsString()); } + @Test public void testParseEmptyWhitespaceInput() { JsonElement e = JsonParser.parseString(" "); assertTrue(e.isJsonNull()); } + @Test public void testParseUnquotedSingleWordStringFails() { assertEquals("Test", JsonParser.parseString("Test").getAsString()); } + @Test public void testParseUnquotedMultiWordStringFails() { String unquotedSentence = "Test is a test..blah blah"; try { @@ -79,6 +100,7 @@ public void testParseUnquotedMultiWordStringFails() { } catch (JsonSyntaxException expected) { } } + @Test public void testParseMixedArray() { String json = "[{},13,\"stringValue\"]"; JsonElement e = JsonParser.parseString(json); @@ -90,6 +112,75 @@ public void testParseMixedArray() { assertEquals("stringValue", array.get(2).getAsString()); } + @Test + public void testParseReader() { + StringReader reader = new StringReader("{a:10,b:'c'}"); + JsonElement e = JsonParser.parseReader(reader); + assertTrue(e.isJsonObject()); + assertEquals(10, e.getAsJsonObject().get("a").getAsInt()); + assertEquals("c", e.getAsJsonObject().get("b").getAsString()); + } + + @Test + public void testParseReaderMultipleTopLevel() { + Reader reader = new StringReader("{}1"); + try { + JsonParser.parseReader(reader); + fail(); + } catch (JsonSyntaxException expected) { + assertEquals("Did not consume the entire document.", expected.getMessage()); + } + } + + @Test + public void testParseReaderMultipleTopLevelNull() { + Reader reader = new StringReader("null[]"); + try { + JsonParser.parseReader(reader); + fail(); + } catch (JsonSyntaxException expected) { + assertEquals("Did not consume the entire document.", expected.getMessage()); + } + } + + @Test + public void testParseJsonReaderMultipleTopLevel() { + String json = "{}1"; + JsonReader jsonReader = new JsonReader(new StringReader(json)); + JsonElement element = JsonParser.parseReader(jsonReader); + assertEquals(new JsonObject(), element); + + element = JsonParser.parseReader(jsonReader); + assertEquals(new JsonPrimitive(1), element); + + try { + JsonParser.parseReader(jsonReader); + fail(); + } catch (IllegalStateException expected) { + assertEquals("Unexpected token: END_DOCUMENT", expected.getMessage()); + } + } + + @Test + public void testReadWriteTwoObjects() throws Exception { + 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, false); + JsonElement element2 = Streams.parse(parser, false); + BagOfPrimitives actualOne = gson.fromJson(element1, BagOfPrimitives.class); + assertEquals("one", actualOne.stringValue); + BagOfPrimitives actualTwo = gson.fromJson(element2, BagOfPrimitives.class); + assertEquals("two", actualTwo.stringValue); + } + private static String repeat(String s, int times) { StringBuilder stringBuilder = new StringBuilder(s.length() * times); for (int i = 0; i < times; i++) { @@ -99,6 +190,7 @@ private static String repeat(String s, int times) { } /** Deeply nested JSON arrays should not cause {@link StackOverflowError} */ + @Test public void testParseDeeplyNestedArrays() throws IOException { int times = 10000; // [[[ ... ]]] @@ -118,6 +210,7 @@ public void testParseDeeplyNestedArrays() throws IOException { } /** Deeply nested JSON objects should not cause {@link StackOverflowError} */ + @Test public void testParseDeeplyNestedObjects() throws IOException { int times = 10000; // {"a":{"a": ... {"a":null} ... }} @@ -137,31 +230,4 @@ public void testParseDeeplyNestedObjects() throws IOException { } assertEquals(times, actualTimes); } - - public void testParseReader() { - StringReader reader = new StringReader("{a:10,b:'c'}"); - JsonElement e = JsonParser.parseReader(reader); - assertTrue(e.isJsonObject()); - assertEquals(10, e.getAsJsonObject().get("a").getAsInt()); - assertEquals("c", e.getAsJsonObject().get("b").getAsString()); - } - - public void testReadWriteTwoObjects() throws Exception { - 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); - JsonElement element2 = Streams.parse(parser); - BagOfPrimitives actualOne = gson.fromJson(element1, BagOfPrimitives.class); - assertEquals("one", actualOne.stringValue); - BagOfPrimitives actualTwo = gson.fromJson(element2, BagOfPrimitives.class); - assertEquals("two", actualTwo.stringValue); - } } diff --git a/gson/src/test/java/com/google/gson/functional/JsonParserTest.java b/gson/src/test/java/com/google/gson/functional/GsonParsingTest.java similarity index 97% rename from gson/src/test/java/com/google/gson/functional/JsonParserTest.java rename to gson/src/test/java/com/google/gson/functional/GsonParsingTest.java index 965140ba08..114e1f5555 100644 --- a/gson/src/test/java/com/google/gson/functional/JsonParserTest.java +++ b/gson/src/test/java/com/google/gson/functional/GsonParsingTest.java @@ -26,22 +26,20 @@ import com.google.gson.common.TestTypes.BagOfPrimitives; import com.google.gson.common.TestTypes.Nested; import com.google.gson.reflect.TypeToken; - -import junit.framework.TestCase; - import java.io.StringReader; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; import java.util.Map; +import junit.framework.TestCase; /** - * Functional tests for that use JsonParser and related Gson methods + * Functional tests for Gson parsing methods * * @author Inderjeet Singh * @author Joel Leitch */ -public class JsonParserTest extends TestCase { +public class GsonParsingTest extends TestCase { private Gson gson; @Override diff --git a/gson/src/test/java/com/google/gson/functional/RuntimeTypeAdapterFactoryFunctionalTest.java b/gson/src/test/java/com/google/gson/functional/RuntimeTypeAdapterFactoryFunctionalTest.java deleted file mode 100644 index 0c6bd07a5a..0000000000 --- a/gson/src/test/java/com/google/gson/functional/RuntimeTypeAdapterFactoryFunctionalTest.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.gson.functional; - -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; - -import junit.framework.TestCase; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.TypeAdapter; -import com.google.gson.TypeAdapterFactory; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.internal.Streams; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; - -/** - * Functional tests for the RuntimeTypeAdapterFactory feature in extras. - */ -public final class RuntimeTypeAdapterFactoryFunctionalTest extends TestCase { - - private final Gson gson = new Gson(); - - /** - * This test also ensures that {@link TypeAdapterFactory} registered through {@link JsonAdapter} - * work correctly for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)}. - */ - public void testSubclassesAutomaticallySerialized() throws Exception { - Shape shape = new Circle(25); - String json = gson.toJson(shape); - shape = gson.fromJson(json, Shape.class); - assertEquals(25, ((Circle)shape).radius); - - shape = new Square(15); - json = gson.toJson(shape); - shape = gson.fromJson(json, Shape.class); - assertEquals(15, ((Square)shape).side); - assertEquals(ShapeType.SQUARE, shape.type); - } - - @JsonAdapter(Shape.JsonAdapterFactory.class) - static class Shape { - final ShapeType type; - Shape(ShapeType type) { this.type = type; } - private static final class JsonAdapterFactory extends RuntimeTypeAdapterFactory { - public JsonAdapterFactory() { - super(Shape.class, "type"); - registerSubtype(Circle.class, ShapeType.CIRCLE.toString()); - registerSubtype(Square.class, ShapeType.SQUARE.toString()); - } - } - } - - public enum ShapeType { - SQUARE, CIRCLE - } - - private static final class Circle extends Shape { - final int radius; - Circle(int radius) { super(ShapeType.CIRCLE); this.radius = radius; } - } - - private static final class Square extends Shape { - final int side; - Square(int side) { super(ShapeType.SQUARE); this.side = side; } - } - - // Copied from the extras package - static class RuntimeTypeAdapterFactory implements TypeAdapterFactory { - private final Class baseType; - private final String typeFieldName; - private final Map> labelToSubtype = new LinkedHashMap<>(); - private final Map, String> subtypeToLabel = new LinkedHashMap<>(); - - protected RuntimeTypeAdapterFactory(Class baseType, String typeFieldName) { - if (typeFieldName == null || baseType == null) { - throw new NullPointerException(); - } - this.baseType = baseType; - this.typeFieldName = typeFieldName; - } - - /** - * Creates a new runtime type adapter using for {@code baseType} using {@code - * typeFieldName} as the type field name. Type field names are case sensitive. - */ - public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { - return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName); - } - - /** - * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as - * the type field name. - */ - public static RuntimeTypeAdapterFactory of(Class baseType) { - return new RuntimeTypeAdapterFactory<>(baseType, "type"); - } - - /** - * Registers {@code type} identified by {@code label}. Labels are case - * sensitive. - * - * @throws IllegalArgumentException if either {@code type} or {@code label} - * have already been registered on this type adapter. - */ - public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { - if (type == null || label == null) { - throw new NullPointerException(); - } - if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { - throw new IllegalArgumentException("types and labels must be unique"); - } - labelToSubtype.put(label, type); - subtypeToLabel.put(type, label); - return this; - } - - /** - * Registers {@code type} identified by its {@link Class#getSimpleName simple - * name}. Labels are case sensitive. - * - * @throws IllegalArgumentException if either {@code type} or its simple name - * have already been registered on this type adapter. - */ - public RuntimeTypeAdapterFactory registerSubtype(Class type) { - return registerSubtype(type, type.getSimpleName()); - } - - @Override public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getRawType() != baseType) { - return null; - } - - final Map> labelToDelegate = new LinkedHashMap<>(); - final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); - for (Map.Entry> entry : labelToSubtype.entrySet()) { - TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); - labelToDelegate.put(entry.getKey(), delegate); - subtypeToDelegate.put(entry.getValue(), delegate); - } - - return new TypeAdapter() { - @Override public R read(JsonReader in) throws IOException { - JsonElement jsonElement = Streams.parse(in); - JsonElement labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); - if (labelJsonElement == null) { - throw new JsonParseException("cannot deserialize " + baseType - + " because it does not define a field named " + typeFieldName); - } - String label = labelJsonElement.getAsString(); - @SuppressWarnings("unchecked") // registration requires that subtype extends T - TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); - if (delegate == null) { - throw new JsonParseException("cannot deserialize " + baseType + " subtype named " - + label + "; did you forget to register a subtype?"); - } - return delegate.fromJsonTree(jsonElement); - } - - @Override public void write(JsonWriter out, R value) throws IOException { - Class srcType = value.getClass(); - String label = subtypeToLabel.get(srcType); - @SuppressWarnings("unchecked") // registration requires that subtype extends T - TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); - if (delegate == null) { - throw new JsonParseException("cannot serialize " + srcType.getName() - + "; did you forget to register a subtype?"); - } - JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); - if (!jsonObject.has(typeFieldName)) { - JsonObject clone = new JsonObject(); - clone.add(typeFieldName, new JsonPrimitive(label)); - for (Map.Entry e : jsonObject.entrySet()) { - clone.add(e.getKey(), e.getValue()); - } - jsonObject = clone; - } - Streams.write(jsonObject, out); - } - }; - } - } -} 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 a755bd8349..6c3b30dcc3 100644 --- a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java +++ b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java @@ -402,7 +402,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); } };