raw) {
+ throw new UnsupportedOperationException(
+ "Records are not supported on this JVM, this method should not be called");
+ }
+
+ @Override
+ public Method getAccessor(Class> raw, Field field) {
+ throw new UnsupportedOperationException(
+ "Records are not supported on this JVM, this method should not be called");
+ }
+ }
}
diff --git a/gson/src/main/java/com/google/gson/reflect/TypeToken.java b/gson/src/main/java/com/google/gson/reflect/TypeToken.java
index 547e5f5b..39e81f33 100644
--- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java
+++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java
@@ -32,7 +32,7 @@ import java.util.Objects;
* runtime.
*
* For example, to create a type literal for {@code List}, you can
- * create an empty anonymous inner class:
+ * create an empty anonymous class:
*
*
* {@code TypeToken> list = new TypeToken>() {};}
@@ -43,6 +43,11 @@ import java.util.Objects;
* might expect, which gives a false sense of type-safety at compilation time
* and can lead to an unexpected {@code ClassCastException} at runtime.
*
+ * If the type arguments of the parameterized type are only available at
+ * runtime, for example when you want to create a {@code List} based on
+ * a {@code Class} representing the element type, the method
+ * {@link #getParameterized(Type, Type...)} can be used.
+ *
* @author Bob Lee
* @author Sven Mawson
* @author Jesse Wilson
@@ -317,8 +322,17 @@ public class TypeToken {
}
/**
- * Gets type literal for the parameterized type represented by applying {@code typeArguments} to
- * {@code rawType}.
+ * Gets a type literal for the parameterized type represented by applying {@code typeArguments} to
+ * {@code rawType}. This is mainly intended for situations where the type arguments are not
+ * available at compile time. The following example shows how a type token for {@code Map}
+ * can be created:
+ * {@code
+ * Class keyClass = ...;
+ * Class valueClass = ...;
+ * TypeToken> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
+ * }
+ * As seen here the result is a {@code TypeToken>}; this method cannot provide any type safety,
+ * and care must be taken to pass in the correct number of type arguments.
*
* @throws IllegalArgumentException
* If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for
diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java
index 8d708ef9..7557426f 100644
--- a/gson/src/main/java/com/google/gson/stream/JsonReader.java
+++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java
@@ -33,7 +33,7 @@ import java.util.Objects;
* depth-first order, the same order that they appear in the JSON document.
* Within JSON objects, name/value pairs are represented by a single token.
*
- * Parsing JSON
+ * Parsing JSON
* To create a recursive descent parser for your own JSON streams, first create
* an entry point method that creates a {@code JsonReader}.
*
@@ -62,7 +62,7 @@ import java.util.Objects;
* Null literals can be consumed using either {@link #nextNull()} or {@link
* #skipValue()}.
*
- * Example
+ * Example
* Suppose we'd like to parse a stream of messages such as the following: {@code
* [
* {
@@ -161,7 +161,7 @@ import java.util.Objects;
* return new User(username, followersCount);
* }}
*
- * Number Handling
+ * Number Handling
* This reader permits numeric values to be read as strings and string values to
* be read as numbers. For example, both elements of the JSON array {@code
* [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
@@ -171,7 +171,7 @@ import java.util.Objects;
* precision loss, extremely large values should be written and read as strings
* in JSON.
*
- * Non-Execute Prefix
+ * Non-Execute Prefix
* Web servers that serve private data using JSON may be vulnerable to Cross-site
* request forgery attacks. In such an attack, a malicious site gains access
@@ -229,6 +229,8 @@ public class JsonReader implements Closeable {
/** True to accept non-spec compliant JSON */
private boolean lenient = DefaultConfig.DEFAULT_LENIENT;
+ private boolean serializeSpecialFloatingPointValues = DefaultConfig.DEFAULT_SPECIALIZE_FLOAT_VALUES;
+
static final int BUFFER_SIZE = 1024;
/**
* Use a manual buffer to easily read and unread upcoming characters, and
@@ -331,6 +333,7 @@ public class JsonReader implements Closeable {
*/
public final void setLenient(boolean lenient) {
this.lenient = lenient;
+ if (lenient) serializeSpecialFloatingPointValues = true;
}
/**
@@ -340,6 +343,14 @@ public class JsonReader implements Closeable {
return lenient;
}
+ public void setSerializeSpecialFloatingPointValues(boolean serializeSpecialFloatingPointValues) {
+ this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
+ }
+
+ public boolean isSerializeSpecialFloatingPointValues() {
+ return serializeSpecialFloatingPointValues;
+ }
+
/**
* Consumes the next token from the JSON stream and asserts that it is the
* beginning of a new array.
@@ -788,10 +799,9 @@ public class JsonReader implements Closeable {
}
/**
- * Returns the next token, a {@link com.google.gson.stream.JsonToken#NAME property name}, and
- * consumes it.
+ * Returns the next token, a {@link JsonToken#NAME property name}, and consumes it.
*
- * @throws java.io.IOException if the next token in the stream is not a property
+ * @throws IOException if the next token in the stream is not a property
* name.
*/
public String nextName() throws IOException {
@@ -815,7 +825,7 @@ public class JsonReader implements Closeable {
}
/**
- * Returns the {@link com.google.gson.stream.JsonToken#STRING string} value of the next token,
+ * Returns the {@link JsonToken#STRING string} value of the next token,
* consuming it. If the next token is a number, this method will return its
* string form.
*
@@ -851,7 +861,7 @@ public class JsonReader implements Closeable {
}
/**
- * Returns the {@link com.google.gson.stream.JsonToken#BOOLEAN boolean} value of the next token,
+ * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
* consuming it.
*
* @throws IllegalStateException if the next token is not a boolean or if
@@ -895,7 +905,7 @@ public class JsonReader implements Closeable {
}
/**
- * Returns the {@link com.google.gson.stream.JsonToken#NUMBER double} value of the next token,
+ * Returns the {@link JsonToken#NUMBER double} value of the next token,
* consuming it. If the next token is a string, this method will attempt to
* parse it as a double using {@link Double#parseDouble(String)}.
*
@@ -930,7 +940,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_BUFFERED;
double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
- if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) {
+ if (!serializeSpecialFloatingPointValues && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new MalformedJsonException(
"JSON forbids NaN and infinities: " + result + locationString());
}
@@ -941,7 +951,7 @@ public class JsonReader implements Closeable {
}
/**
- * Returns the {@link com.google.gson.stream.JsonToken#NUMBER long} value of the next token,
+ * Returns the {@link JsonToken#NUMBER long} value of the next token,
* consuming it. If the next token is a string, this method will attempt to
* parse it as a long. If the next token's numeric value cannot be exactly
* represented by a Java {@code long}, this method throws.
@@ -1174,7 +1184,7 @@ public class JsonReader implements Closeable {
}
/**
- * Returns the {@link com.google.gson.stream.JsonToken#NUMBER int} value of the next token,
+ * Returns the {@link JsonToken#NUMBER int} value of the next token,
* consuming it. If the next token is a string, this method will attempt to
* parse it as an int. If the next token's numeric value cannot be exactly
* represented by a Java {@code int}, this method throws.
@@ -1238,7 +1248,7 @@ public class JsonReader implements Closeable {
}
/**
- * Closes this JSON reader and the underlying {@link java.io.Reader}.
+ * Closes this JSON reader and the underlying {@link Reader}.
*/
@Override public void close() throws IOException {
peeked = PEEKED_NONE;
@@ -1248,9 +1258,18 @@ public class JsonReader implements Closeable {
}
/**
- * Skips the next value recursively. If it is an object or array, all nested
- * elements are skipped. This method is intended for use when the JSON token
- * stream contains unrecognized or unhandled values.
+ * Skips the next value recursively. This method is intended for use when
+ * the JSON token stream contains unrecognized or unhandled values.
+ *
+ * The behavior depends on the type of the next JSON token:
+ *
+ * Start of a JSON array or object: It and all of its nested values are skipped.
+ * Primitive value (for example a JSON number): The primitive value is skipped.
+ * Property name: Only the name but not the value of the property is skipped.
+ * {@code skipValue()} has to be called again to skip the property value as well.
+ * End of a JSON array or object: Only this end token is skipped and an exception is thrown.
+ * End of JSON document: Skipping has no effect on the state, but an exception is thrown.
+ *
*/
public void skipValue() throws IOException {
int count = 0;
@@ -1260,33 +1279,69 @@ public class JsonReader implements Closeable {
p = doPeek();
}
- if (p == PEEKED_BEGIN_ARRAY) {
- push(JsonScope.EMPTY_ARRAY);
- count++;
- } else if (p == PEEKED_BEGIN_OBJECT) {
- push(JsonScope.EMPTY_OBJECT);
- count++;
- } else if (p == PEEKED_END_ARRAY) {
- stackSize--;
- count--;
- } else if (p == PEEKED_END_OBJECT) {
- stackSize--;
- count--;
- } else if (p == PEEKED_UNQUOTED_NAME || p == PEEKED_UNQUOTED) {
- skipUnquotedValue();
- } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_SINGLE_QUOTED_NAME) {
- skipQuotedValue('\'');
- } else if (p == PEEKED_DOUBLE_QUOTED || p == PEEKED_DOUBLE_QUOTED_NAME) {
- skipQuotedValue('"');
- } else if (p == PEEKED_NUMBER) {
- pos += peekedNumberLength;
+ switch (p) {
+ case PEEKED_BEGIN_ARRAY:
+ push(JsonScope.EMPTY_ARRAY);
+ count++;
+ break;
+ case PEEKED_BEGIN_OBJECT:
+ push(JsonScope.EMPTY_OBJECT);
+ count++;
+ break;
+ case PEEKED_END_ARRAY:
+ stackSize--;
+ count--;
+ break;
+ case PEEKED_END_OBJECT:
+ // Only update when object end is explicitly skipped, otherwise stack is not updated anyways
+ if (count == 0) {
+ pathNames[stackSize - 1] = null; // Free the last path name so that it can be garbage collected
+ }
+ stackSize--;
+ count--;
+ break;
+ case PEEKED_UNQUOTED:
+ skipUnquotedValue();
+ break;
+ case PEEKED_SINGLE_QUOTED:
+ skipQuotedValue('\'');
+ break;
+ case PEEKED_DOUBLE_QUOTED:
+ skipQuotedValue('"');
+ break;
+ case PEEKED_UNQUOTED_NAME:
+ skipUnquotedValue();
+ // Only update when name is explicitly skipped, otherwise stack is not updated anyways
+ if (count == 0) {
+ pathNames[stackSize - 1] = "";
+ }
+ break;
+ case PEEKED_SINGLE_QUOTED_NAME:
+ skipQuotedValue('\'');
+ // Only update when name is explicitly skipped, otherwise stack is not updated anyways
+ if (count == 0) {
+ pathNames[stackSize - 1] = "";
+ }
+ break;
+ case PEEKED_DOUBLE_QUOTED_NAME:
+ skipQuotedValue('"');
+ // Only update when name is explicitly skipped, otherwise stack is not updated anyways
+ if (count == 0) {
+ pathNames[stackSize - 1] = "";
+ }
+ break;
+ case PEEKED_NUMBER:
+ pos += peekedNumberLength;
+ break;
+ case PEEKED_EOF:
+ throw new IllegalStateException("Attempt to skip led outside the document");
+ // For all other tokens there is nothing to do; token has already been consumed from underlying reader
}
peeked = PEEKED_NONE;
} while (count > 0);
- if (count < 0) throw new IllegalStateException("Attempt to skip led outside its parent");
pathIndices[stackSize - 1]++;
- pathNames[stackSize - 1] = "null";
+ if (count < 0) throw new IllegalStateException("Attempt to skip led outside its parent");
}
private void push(int newTop) {
@@ -1522,7 +1577,7 @@ public class JsonReader implements Closeable {
* For JSON arrays the path points to the index of the previous element.
* If no element has been consumed yet it uses the index 0 (even if there are no elements).
* For JSON objects the path points to the last property, or to the current
- * property if its value has not been consumed yet.
+ * property if its name has already been consumed.
*
*
* This method can be useful to add additional context to exception messages
@@ -1539,7 +1594,7 @@ public class JsonReader implements Closeable {
*
For JSON arrays the path points to the index of the next element (even
* if there are no further elements).
* For JSON objects the path points to the last property, or to the current
- * property if its value has not been consumed yet.
+ * property if its name has already been consumed.
*
*
* This method can be useful to add additional context to exception messages
diff --git a/gson/src/main/java/com/google/gson/stream/JsonWriter.java b/gson/src/main/java/com/google/gson/stream/JsonWriter.java
index 387011de..44c7b08b 100644
--- a/gson/src/main/java/com/google/gson/stream/JsonWriter.java
+++ b/gson/src/main/java/com/google/gson/stream/JsonWriter.java
@@ -43,7 +43,7 @@ import java.util.regex.Pattern;
* literal values (strings, numbers, booleans and nulls) as well as the begin
* and end delimiters of objects and arrays.
*
- *
Encoding JSON
+ * Encoding JSON
* To encode your data as JSON, create a new {@code JsonWriter}. Call methods
* on the writer as you walk the structure's contents, nesting arrays and objects
* as necessary:
@@ -59,7 +59,7 @@ import java.util.regex.Pattern;
* Finally close the object using {@link #endObject()}.
*
*
- * Example
+ * Example
* Suppose we'd like to encode a stream of messages such as the following: {@code
* [
* {
@@ -193,6 +193,8 @@ public class JsonWriter implements Closeable, Flushable {
private boolean lenient = DefaultConfig.DEFAULT_LENIENT;
+ private boolean serializeSpecialFloatingPointValues = DefaultConfig.DEFAULT_SPECIALIZE_FLOAT_VALUES;
+
private boolean omitQuotes = DefaultConfig.DEFAULT_OMIT_QUOTES;
private boolean htmlSafe;
@@ -242,6 +244,7 @@ public class JsonWriter implements Closeable, Flushable {
*/
public final void setLenient(boolean lenient) {
this.lenient = lenient;
+ if (lenient) this.serializeSpecialFloatingPointValues = true;
}
/**
@@ -251,6 +254,14 @@ public class JsonWriter implements Closeable, Flushable {
return lenient;
}
+ public void setSerializeSpecialFloatingPointValues(boolean serializeSpecialFloatingPointValues) {
+ this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
+ }
+
+ public boolean isSerializeSpecialFloatingPointValues() {
+ return serializeSpecialFloatingPointValues;
+ }
+
/**
* Configure this writer to emit JSON that's safe for direct inclusion in HTML
* and XML documents. This escapes the HTML characters {@code <}, {@code >},
@@ -490,6 +501,7 @@ public class JsonWriter implements Closeable, Flushable {
* @return this writer.
* @throws UnsupportedOperationException if this writer does not support
* writing raw JSON values.
+ * @since 2.4
*/
public JsonWriter jsonValue(String value) throws IOException {
if (value == null) {
@@ -536,6 +548,7 @@ public class JsonWriter implements Closeable, Flushable {
* Encodes {@code value}.
*
* @return this writer.
+ * @since 2.7
*/
public JsonWriter value(Boolean value) throws IOException {
if (value == null) {
@@ -556,10 +569,11 @@ public class JsonWriter implements Closeable, Flushable {
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
* #setLenient(boolean) lenient}.
+ * @since 2.9.1
*/
public JsonWriter value(float value) throws IOException {
writeDeferredName();
- if (!lenient && (Float.isNaN(value) || Float.isInfinite(value))) {
+ if (!serializeSpecialFloatingPointValues && (Float.isNaN(value) || Float.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
@@ -578,7 +592,7 @@ public class JsonWriter implements Closeable, Flushable {
*/
public JsonWriter value(double value) throws IOException {
writeDeferredName();
- if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) {
+ if (!serializeSpecialFloatingPointValues && (Double.isNaN(value) || Double.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
@@ -628,7 +642,7 @@ public class JsonWriter implements Closeable, Flushable {
writeDeferredName();
String string = value.toString();
if (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN")) {
- if (!lenient) {
+ if (!serializeSpecialFloatingPointValues) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + string);
}
} else {
diff --git a/gson/src/test/java/com/google/gson/GsonBuilderTest.java b/gson/src/test/java/com/google/gson/GsonBuilderTest.java
index 9a7adbae..e1a013b5 100644
--- a/gson/src/test/java/com/google/gson/GsonBuilderTest.java
+++ b/gson/src/test/java/com/google/gson/GsonBuilderTest.java
@@ -16,20 +16,25 @@
package com.google.gson;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.fail;
+
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
-import junit.framework.TestCase;
+import org.junit.Test;
/**
* Unit tests for {@link GsonBuilder}.
*
* @author Inderjeet Singh
*/
-public class GsonBuilderTest extends TestCase {
+public class GsonBuilderTest {
private static final TypeAdapter NULL_TYPE_ADAPTER = new TypeAdapter() {
@Override public void write(JsonWriter out, Object value) {
throw new AssertionError();
@@ -39,6 +44,7 @@ public class GsonBuilderTest extends TestCase {
}
};
+ @Test
public void testCreatingMoreThanOnce() {
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
@@ -61,6 +67,7 @@ public class GsonBuilderTest extends TestCase {
* Gson instances should not be affected by subsequent modification of GsonBuilder
* which created them.
*/
+ @Test
public void testModificationAfterCreate() {
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
@@ -136,6 +143,7 @@ public class GsonBuilderTest extends TestCase {
}
}
+ @Test
public void testExcludeFieldsWithModifiers() {
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.VOLATILE, Modifier.PRIVATE)
@@ -151,6 +159,7 @@ public class GsonBuilderTest extends TestCase {
String d = "d";
}
+ @Test
public void testTransientFieldExclusion() {
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers()
@@ -162,6 +171,7 @@ public class GsonBuilderTest extends TestCase {
transient String a = "a";
}
+ @Test
public void testRegisterTypeAdapterForCoreType() {
Type[] types = {
byte.class,
@@ -176,6 +186,7 @@ public class GsonBuilderTest extends TestCase {
}
}
+ @Test
public void testDisableJdkUnsafe() {
Gson gson = new GsonBuilder()
.disableJdkUnsafe()
@@ -198,4 +209,22 @@ public class GsonBuilderTest extends TestCase {
public ClassWithoutNoArgsConstructor(String s) {
}
}
+
+ @Test
+ public void testSetVersionInvalid() {
+ GsonBuilder builder = new GsonBuilder();
+ try {
+ builder.setVersion(Double.NaN);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid version: NaN", e.getMessage());
+ }
+
+ try {
+ builder.setVersion(-0.1);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertEquals("Invalid version: -0.1", e.getMessage());
+ }
+ }
}
diff --git a/gson/src/test/java/com/google/gson/JsonArrayAsListTest.java b/gson/src/test/java/com/google/gson/JsonArrayAsListTest.java
new file mode 100644
index 00000000..a1786ce6
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonArrayAsListTest.java
@@ -0,0 +1,285 @@
+package com.google.gson;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.gson.common.MoreAsserts;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+/**
+ * Tests for {@link JsonArray#asList()}.
+ */
+public class JsonArrayAsListTest {
+ @Test
+ public void testGet() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ assertEquals(new JsonPrimitive(1), list.get(0));
+
+ try {
+ list.get(-1);
+ fail();
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ list.get(2);
+ fail();
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ a.add((JsonElement) null);
+ assertEquals(JsonNull.INSTANCE, list.get(1));
+ }
+
+ @Test
+ public void testSize() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ assertEquals(1, list.size());
+ list.add(new JsonPrimitive(2));
+ assertEquals(2, list.size());
+ }
+
+ @Test
+ public void testSet() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ JsonElement old = list.set(0, new JsonPrimitive(2));
+ assertEquals(new JsonPrimitive(1), old);
+ assertEquals(new JsonPrimitive(2), list.get(0));
+ assertEquals(new JsonPrimitive(2), a.get(0));
+
+ try {
+ list.set(-1, new JsonPrimitive(1));
+ fail();
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ list.set(2, new JsonPrimitive(1));
+ fail();
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ list.set(0, null);
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("Element must be non-null", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAdd() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ list.add(0, new JsonPrimitive(2));
+ list.add(1, new JsonPrimitive(3));
+ assertTrue(list.add(new JsonPrimitive(4)));
+ assertTrue(list.add(JsonNull.INSTANCE));
+
+ List expectedList = Arrays.asList(
+ new JsonPrimitive(2),
+ new JsonPrimitive(3),
+ new JsonPrimitive(1),
+ new JsonPrimitive(4),
+ JsonNull.INSTANCE
+ );
+ assertEquals(expectedList, list);
+
+ try {
+ list.set(-1, new JsonPrimitive(1));
+ fail();
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ list.set(list.size(), new JsonPrimitive(1));
+ fail();
+ } catch (IndexOutOfBoundsException e) {
+ }
+
+ try {
+ list.add(0, null);
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("Element must be non-null", e.getMessage());
+ }
+ try {
+ list.add(null);
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("Element must be non-null", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testAddAll() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ list.addAll(Arrays.asList(new JsonPrimitive(2), new JsonPrimitive(3)));
+
+ List expectedList = Arrays.asList(
+ new JsonPrimitive(1),
+ new JsonPrimitive(2),
+ new JsonPrimitive(3)
+ );
+ assertEquals(expectedList, list);
+
+ try {
+ list.addAll(0, Collections.singletonList(null));
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("Element must be non-null", e.getMessage());
+ }
+ try {
+ list.addAll(Collections.singletonList(null));
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("Element must be non-null", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testRemoveIndex() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ assertEquals(new JsonPrimitive(1), list.remove(0));
+ assertEquals(0, list.size());
+ assertEquals(0, a.size());
+
+ try {
+ list.remove(0);
+ fail();
+ } catch (IndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testRemoveElement() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ assertTrue(list.remove(new JsonPrimitive(1)));
+ assertEquals(0, list.size());
+ assertEquals(0, a.size());
+
+ assertFalse(list.remove(new JsonPrimitive(1)));
+ assertFalse(list.remove(null));
+ }
+
+ @Test
+ public void testClear() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ list.clear();
+ assertEquals(0, list.size());
+ assertEquals(0, a.size());
+ }
+
+ @Test
+ public void testContains() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ assertTrue(list.contains(new JsonPrimitive(1)));
+ assertFalse(list.contains(new JsonPrimitive(2)));
+ assertFalse(list.contains(null));
+
+ @SuppressWarnings({"unlikely-arg-type", "CollectionIncompatibleType"})
+ boolean containsInt = list.contains(1); // should only contain JsonPrimitive(1)
+ assertFalse(containsInt);
+ }
+
+ @Test
+ public void testIndexOf() {
+ JsonArray a = new JsonArray();
+ // Add the same value twice to test indexOf vs. lastIndexOf
+ a.add(1);
+ a.add(1);
+
+ List list = a.asList();
+ assertEquals(0, list.indexOf(new JsonPrimitive(1)));
+ assertEquals(-1, list.indexOf(new JsonPrimitive(2)));
+ assertEquals(-1, list.indexOf(null));
+
+ @SuppressWarnings({"unlikely-arg-type", "CollectionIncompatibleType"})
+ int indexOfInt = list.indexOf(1); // should only contain JsonPrimitive(1)
+ assertEquals(-1, indexOfInt);
+
+ assertEquals(1, list.lastIndexOf(new JsonPrimitive(1)));
+ assertEquals(-1, list.lastIndexOf(new JsonPrimitive(2)));
+ assertEquals(-1, list.lastIndexOf(null));
+ }
+
+ @Test
+ public void testToArray() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ assertArrayEquals(new Object[] {new JsonPrimitive(1)}, list.toArray());
+
+ JsonElement[] array = list.toArray(new JsonElement[0]);
+ assertArrayEquals(new Object[] {new JsonPrimitive(1)}, array);
+
+ array = new JsonElement[1];
+ assertSame(array, list.toArray(array));
+ assertArrayEquals(new Object[] {new JsonPrimitive(1)}, array);
+
+ array = new JsonElement[] {null, new JsonPrimitive(2)};
+ assertSame(array, list.toArray(array));
+ // Should have set existing array element to null
+ assertArrayEquals(new Object[] {new JsonPrimitive(1), null}, array);
+ }
+
+ @Test
+ public void testEqualsHashCode() {
+ JsonArray a = new JsonArray();
+ a.add(1);
+
+ List list = a.asList();
+ MoreAsserts.assertEqualsAndHashCode(list, Collections.singletonList(new JsonPrimitive(1)));
+ assertFalse(list.equals(Collections.emptyList()));
+ assertFalse(list.equals(Collections.singletonList(new JsonPrimitive(2))));
+ }
+
+ /** Verify that {@code JsonArray} updates are visible to view and vice versa */
+ @Test
+ public void testViewUpdates() {
+ JsonArray a = new JsonArray();
+ List list = a.asList();
+
+ a.add(1);
+ assertEquals(1, list.size());
+ assertEquals(new JsonPrimitive(1), list.get(0));
+
+ list.add(new JsonPrimitive(2));
+ assertEquals(2, a.size());
+ assertEquals(new JsonPrimitive(2), a.get(1));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonArrayTest.java b/gson/src/test/java/com/google/gson/JsonArrayTest.java
index 70398460..45070e3f 100644
--- a/gson/src/test/java/com/google/gson/JsonArrayTest.java
+++ b/gson/src/test/java/com/google/gson/JsonArrayTest.java
@@ -16,18 +16,26 @@
package com.google.gson;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import com.google.gson.common.MoreAsserts;
-import junit.framework.TestCase;
+import java.math.BigInteger;
+import org.junit.Test;
/**
* @author Jesse Wilson
*/
-public final class JsonArrayTest extends TestCase {
+public final class JsonArrayTest {
+ @Test
public void testEqualsOnEmptyArray() {
MoreAsserts.assertEqualsAndHashCode(new JsonArray(), new JsonArray());
}
+ @Test
public void testEqualsNonEmptyArray() {
JsonArray a = new JsonArray();
JsonArray b = new JsonArray();
@@ -50,6 +58,7 @@ public final class JsonArrayTest extends TestCase {
assertFalse(b.equals(a));
}
+ @Test
public void testRemove() {
JsonArray array = new JsonArray();
try {
@@ -67,6 +76,7 @@ public final class JsonArrayTest extends TestCase {
assertTrue(array.contains(a));
}
+ @Test
public void testSet() {
JsonArray array = new JsonArray();
try {
@@ -91,6 +101,7 @@ public final class JsonArrayTest extends TestCase {
assertEquals(1, array.size());
}
+ @Test
public void testDeepCopy() {
JsonArray original = new JsonArray();
JsonArray firstEntry = new JsonArray();
@@ -106,6 +117,7 @@ public final class JsonArrayTest extends TestCase {
assertEquals(0, copy.get(0).getAsJsonArray().size());
}
+ @Test
public void testIsEmpty() {
JsonArray array = new JsonArray();
assertTrue(array.isEmpty());
@@ -118,6 +130,7 @@ public final class JsonArrayTest extends TestCase {
assertTrue(array.isEmpty());
}
+ @Test
public void testFailedGetArrayValues() {
JsonArray jsonArray = new JsonArray();
jsonArray.add(JsonParser.parseString("{" + "\"key1\":\"value1\"," + "\"key2\":\"value2\"," + "\"key3\":\"value3\"," + "\"key4\":\"value4\"" + "}"));
@@ -182,6 +195,7 @@ public final class JsonArrayTest extends TestCase {
}
}
+ @Test
public void testGetAs_WrongArraySize() {
JsonArray jsonArray = new JsonArray();
try {
@@ -200,4 +214,160 @@ public final class JsonArrayTest extends TestCase {
assertEquals("Array must have size 1, but has size 2", e.getMessage());
}
}
+
+ @Test
+ public void testStringPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add("Hello");
+ jsonArray.add("Goodbye");
+ jsonArray.add("Thank you");
+ jsonArray.add((String) null);
+ jsonArray.add("Yes");
+
+ assertEquals("[\"Hello\",\"Goodbye\",\"Thank you\",null,\"Yes\"]", jsonArray.toString());
+ }
+
+ @Test
+ public void testIntegerPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ int x = 1;
+ jsonArray.add(x);
+
+ x = 2;
+ jsonArray.add(x);
+
+ x = -3;
+ jsonArray.add(x);
+
+ jsonArray.add((Integer) null);
+
+ x = 4;
+ jsonArray.add(x);
+
+ x = 0;
+ jsonArray.add(x);
+
+ assertEquals("[1,2,-3,null,4,0]", jsonArray.toString());
+ }
+
+ @Test
+ public void testDoublePrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ double x = 1.0;
+ jsonArray.add(x);
+
+ x = 2.13232;
+ jsonArray.add(x);
+
+ x = 0.121;
+ jsonArray.add(x);
+
+ jsonArray.add((Double) null);
+
+ x = -0.00234;
+ jsonArray.add(x);
+
+ jsonArray.add((Double) null);
+
+ assertEquals("[1.0,2.13232,0.121,null,-0.00234,null]", jsonArray.toString());
+ }
+
+ @Test
+ public void testBooleanPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add(true);
+ jsonArray.add(true);
+ jsonArray.add(false);
+ jsonArray.add(false);
+ jsonArray.add((Boolean) null);
+ jsonArray.add(true);
+
+ assertEquals("[true,true,false,false,null,true]", jsonArray.toString());
+ }
+
+ @Test
+ public void testCharPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add('a');
+ jsonArray.add('e');
+ jsonArray.add('i');
+ jsonArray.add((char) 111);
+ jsonArray.add((Character) null);
+ jsonArray.add('u');
+ jsonArray.add("and sometimes Y");
+
+ assertEquals("[\"a\",\"e\",\"i\",\"o\",null,\"u\",\"and sometimes Y\"]", jsonArray.toString());
+ }
+
+ @Test
+ public void testMixedPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add('a');
+ jsonArray.add("apple");
+ jsonArray.add(12121);
+ jsonArray.add((char) 111);
+
+ jsonArray.add((Boolean) null);
+ assertEquals(JsonNull.INSTANCE, jsonArray.get(jsonArray.size() - 1));
+
+ jsonArray.add((Character) null);
+ assertEquals(JsonNull.INSTANCE, jsonArray.get(jsonArray.size() - 1));
+
+ jsonArray.add(12.232);
+ jsonArray.add(BigInteger.valueOf(2323));
+
+ assertEquals("[\"a\",\"apple\",12121,\"o\",null,null,12.232,2323]", jsonArray.toString());
+ }
+
+ @Test
+ public void testNullPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add((Character) null);
+ jsonArray.add((Boolean) null);
+ jsonArray.add((Integer) null);
+ jsonArray.add((Double) null);
+ jsonArray.add((Float) null);
+ jsonArray.add((BigInteger) null);
+ jsonArray.add((String) null);
+ jsonArray.add((Boolean) null);
+ jsonArray.add((Number) null);
+
+ assertEquals("[null,null,null,null,null,null,null,null,null]", jsonArray.toString());
+ for (int i = 0; i < jsonArray.size(); i++) {
+ // Verify that they are actually a JsonNull and not a Java null
+ assertEquals(JsonNull.INSTANCE, jsonArray.get(i));
+ }
+ }
+
+ @Test
+ public void testNullJsonElementAddition() {
+ JsonArray jsonArray = new JsonArray();
+ jsonArray.add((JsonElement) null);
+ assertEquals(JsonNull.INSTANCE, jsonArray.get(0));
+ }
+
+ @Test
+ public void testSameAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add('a');
+ jsonArray.add('a');
+ jsonArray.add(true);
+ jsonArray.add(true);
+ jsonArray.add(1212);
+ jsonArray.add(1212);
+ jsonArray.add(34.34);
+ jsonArray.add(34.34);
+ jsonArray.add((Boolean) null);
+ jsonArray.add((Boolean) null);
+
+ assertEquals("[\"a\",\"a\",true,true,1212,1212,34.34,34.34,null,null]", jsonArray.toString());
+ }
}
diff --git a/gson/src/test/java/com/google/gson/JsonObjectAsMapTest.java b/gson/src/test/java/com/google/gson/JsonObjectAsMapTest.java
new file mode 100644
index 00000000..00a89a6f
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonObjectAsMapTest.java
@@ -0,0 +1,287 @@
+package com.google.gson;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.gson.common.MoreAsserts;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import org.junit.Test;
+
+/**
+ * Tests for {@link JsonObject#asMap()}.
+ */
+public class JsonObjectAsMapTest {
+ @Test
+ public void testSize() {
+ JsonObject o = new JsonObject();
+ assertEquals(0, o.asMap().size());
+
+ o.addProperty("a", 1);
+ Map map = o.asMap();
+ assertEquals(1, map.size());
+
+ map.clear();
+ assertEquals(0, map.size());
+ assertEquals(0, o.size());
+ }
+
+ @Test
+ public void testContainsKey() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 1);
+
+ Map map = o.asMap();
+ assertTrue(map.containsKey("a"));
+ assertFalse(map.containsKey("b"));
+ assertFalse(map.containsKey(null));
+ }
+
+ @Test
+ public void testContainsValue() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 1);
+ o.add("b", JsonNull.INSTANCE);
+
+ Map map = o.asMap();
+ assertTrue(map.containsValue(new JsonPrimitive(1)));
+ assertFalse(map.containsValue(new JsonPrimitive(2)));
+ assertFalse(map.containsValue(null));
+
+ @SuppressWarnings({"unlikely-arg-type", "CollectionIncompatibleType"})
+ boolean containsInt = map.containsValue(1); // should only contain JsonPrimitive(1)
+ assertFalse(containsInt);
+ }
+
+ @Test
+ public void testGet() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 1);
+
+ Map map = o.asMap();
+ assertEquals(new JsonPrimitive(1), map.get("a"));
+ assertNull(map.get("b"));
+ assertNull(map.get(null));
+ }
+
+ @Test
+ public void testPut() {
+ JsonObject o = new JsonObject();
+ Map map = o.asMap();
+
+ assertNull(map.put("a", new JsonPrimitive(1)));
+ assertEquals(1, map.size());
+ assertEquals(new JsonPrimitive(1), map.get("a"));
+
+ JsonElement old = map.put("a", new JsonPrimitive(2));
+ assertEquals(new JsonPrimitive(1), old);
+ assertEquals(1, map.size());
+ assertEquals(new JsonPrimitive(2), map.get("a"));
+ assertEquals(new JsonPrimitive(2), o.get("a"));
+
+ assertNull(map.put("b", JsonNull.INSTANCE));
+ assertEquals(JsonNull.INSTANCE, map.get("b"));
+
+ try {
+ map.put(null, new JsonPrimitive(1));
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("key == null", e.getMessage());
+ }
+
+ try {
+ map.put("a", null);
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("value == null", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testRemove() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 1);
+
+ Map map = o.asMap();
+ assertNull(map.remove("b"));
+ assertEquals(1, map.size());
+
+ JsonElement old = map.remove("a");
+ assertEquals(new JsonPrimitive(1), old);
+ assertEquals(0, map.size());
+
+ assertNull(map.remove("a"));
+ assertEquals(0, map.size());
+ assertEquals(0, o.size());
+
+ assertNull(map.remove(null));
+ }
+
+ @Test
+ public void testPutAll() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 1);
+
+ Map otherMap = new HashMap<>();
+ otherMap.put("a", new JsonPrimitive(2));
+ otherMap.put("b", new JsonPrimitive(3));
+
+ Map map = o.asMap();
+ map.putAll(otherMap);
+ assertEquals(2, map.size());
+ assertEquals(new JsonPrimitive(2), map.get("a"));
+ assertEquals(new JsonPrimitive(3), map.get("b"));
+
+ try {
+ map.putAll(Collections.singletonMap(null, new JsonPrimitive(1)));
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("key == null", e.getMessage());
+ }
+
+ try {
+ map.putAll(Collections.singletonMap("a", null));
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("value == null", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testClear() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 1);
+
+ Map map = o.asMap();
+ map.clear();
+ assertEquals(0, map.size());
+ assertEquals(0, o.size());
+ }
+
+ @Test
+ public void testKeySet() {
+ JsonObject o = new JsonObject();
+ o.addProperty("b", 1);
+ o.addProperty("a", 2);
+
+ Map map = o.asMap();
+ Set keySet = map.keySet();
+ // Should contain keys in same order
+ assertEquals(Arrays.asList("b", "a"), new ArrayList<>(keySet));
+
+ // Key set doesn't support insertions
+ try {
+ keySet.add("c");
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+
+ assertTrue(keySet.remove("a"));
+ assertEquals(Collections.singleton("b"), map.keySet());
+ assertEquals(Collections.singleton("b"), o.keySet());
+ }
+
+ @Test
+ public void testValues() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 2);
+ o.addProperty("b", 1);
+
+ Map map = o.asMap();
+ Collection values = map.values();
+ // Should contain values in same order
+ assertEquals(Arrays.asList(new JsonPrimitive(2), new JsonPrimitive(1)), new ArrayList<>(values));
+
+ // Values collection doesn't support insertions
+ try {
+ values.add(new JsonPrimitive(3));
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+
+ assertTrue(values.remove(new JsonPrimitive(2)));
+ assertEquals(Collections.singletonList(new JsonPrimitive(1)), new ArrayList<>(map.values()));
+ assertEquals(1, o.size());
+ assertEquals(new JsonPrimitive(1), o.get("b"));
+ }
+
+ @Test
+ public void testEntrySet() {
+ JsonObject o = new JsonObject();
+ o.addProperty("b", 2);
+ o.addProperty("a", 1);
+
+ Map map = o.asMap();
+ Set> entrySet = map.entrySet();
+
+ List> expectedEntrySet = Arrays.>asList(
+ new SimpleEntry<>("b", new JsonPrimitive(2)),
+ new SimpleEntry<>("a", new JsonPrimitive(1))
+ );
+ // Should contain entries in same order
+ assertEquals(expectedEntrySet, new ArrayList<>(entrySet));
+
+ try {
+ entrySet.add(new SimpleEntry("c", new JsonPrimitive(3)));
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+
+ assertTrue(entrySet.remove(new SimpleEntry<>("a", new JsonPrimitive(1))));
+ assertEquals(Collections.singleton(new SimpleEntry<>("b", new JsonPrimitive(2))), map.entrySet());
+ assertEquals(Collections.singleton(new SimpleEntry<>("b", new JsonPrimitive(2))), o.entrySet());
+
+ // Should return false because entry has already been removed
+ assertFalse(entrySet.remove(new SimpleEntry<>("a", new JsonPrimitive(1))));
+
+ Entry entry = entrySet.iterator().next();
+ JsonElement old = entry.setValue(new JsonPrimitive(3));
+ assertEquals(new JsonPrimitive(2), old);
+ assertEquals(Collections.singleton(new SimpleEntry<>("b", new JsonPrimitive(3))), map.entrySet());
+ assertEquals(Collections.singleton(new SimpleEntry<>("b", new JsonPrimitive(3))), o.entrySet());
+
+ try {
+ entry.setValue(null);
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("value == null", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testEqualsHashCode() {
+ JsonObject o = new JsonObject();
+ o.addProperty("a", 1);
+
+ Map map = o.asMap();
+ MoreAsserts.assertEqualsAndHashCode(map, Collections.singletonMap("a", new JsonPrimitive(1)));
+ assertFalse(map.equals(Collections.emptyMap()));
+ assertFalse(map.equals(Collections.singletonMap("a", new JsonPrimitive(2))));
+ }
+
+ /** Verify that {@code JsonObject} updates are visible to view and vice versa */
+ @Test
+ public void testViewUpdates() {
+ JsonObject o = new JsonObject();
+ Map map = o.asMap();
+
+ o.addProperty("a", 1);
+ assertEquals(1, map.size());
+ assertEquals(new JsonPrimitive(1), map.get("a"));
+
+ map.put("b", new JsonPrimitive(2));
+ assertEquals(2, o.size());
+ assertEquals(new JsonPrimitive(2), o.get("b"));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonObjectTest.java b/gson/src/test/java/com/google/gson/JsonObjectTest.java
index d12d12d8..a0109ba8 100644
--- a/gson/src/test/java/com/google/gson/JsonObjectTest.java
+++ b/gson/src/test/java/com/google/gson/JsonObjectTest.java
@@ -16,6 +16,13 @@
package com.google.gson;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import com.google.gson.common.MoreAsserts;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayDeque;
@@ -27,15 +34,16 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
-import junit.framework.TestCase;
+import org.junit.Test;
/**
* Unit test for the {@link JsonObject} class.
*
* @author Joel Leitch
*/
-public class JsonObjectTest extends TestCase {
+public class JsonObjectTest {
+ @Test
public void testAddingAndRemovingObjectProperties() throws Exception {
JsonObject jsonObj = new JsonObject();
String propertyName = "property";
@@ -54,6 +62,7 @@ public class JsonObjectTest extends TestCase {
assertNull(jsonObj.remove(propertyName));
}
+ @Test
public void testAddingNullPropertyValue() throws Exception {
String propertyName = "property";
JsonObject jsonObj = new JsonObject();
@@ -66,6 +75,7 @@ public class JsonObjectTest extends TestCase {
assertTrue(jsonElement.isJsonNull());
}
+ @Test
public void testAddingNullOrEmptyPropertyName() throws Exception {
JsonObject jsonObj = new JsonObject();
try {
@@ -77,6 +87,7 @@ public class JsonObjectTest extends TestCase {
jsonObj.add(" \t", JsonNull.INSTANCE);
}
+ @Test
public void testAddingBooleanProperties() throws Exception {
String propertyName = "property";
JsonObject jsonObj = new JsonObject();
@@ -89,6 +100,7 @@ public class JsonObjectTest extends TestCase {
assertTrue(jsonElement.getAsBoolean());
}
+ @Test
public void testAddingStringProperties() throws Exception {
String propertyName = "property";
String value = "blah";
@@ -103,6 +115,7 @@ public class JsonObjectTest extends TestCase {
assertEquals(value, jsonElement.getAsString());
}
+ @Test
public void testAddingCharacterProperties() throws Exception {
String propertyName = "property";
char value = 'a';
@@ -124,6 +137,7 @@ public class JsonObjectTest extends TestCase {
/**
* From bug report http://code.google.com/p/google-gson/issues/detail?id=182
*/
+ @Test
public void testPropertyWithQuotes() {
JsonObject jsonObj = new JsonObject();
jsonObj.add("a\"b", new JsonPrimitive("c\"d"));
@@ -134,6 +148,7 @@ public class JsonObjectTest extends TestCase {
/**
* From issue 227.
*/
+ @Test
public void testWritePropertyWithEmptyStringName() {
JsonObject jsonObj = new JsonObject();
jsonObj.add("", new JsonPrimitive(true));
@@ -141,15 +156,18 @@ public class JsonObjectTest extends TestCase {
}
+ @Test
public void testReadPropertyWithEmptyStringName() {
JsonObject jsonObj = JsonParser.parseString("{\"\":true}").getAsJsonObject();
assertEquals(true, jsonObj.get("").getAsBoolean());
}
+ @Test
public void testEqualsOnEmptyObject() {
MoreAsserts.assertEqualsAndHashCode(new JsonObject(), new JsonObject());
}
+ @Test
public void testEqualsNonEmptyObject() {
JsonObject a = new JsonObject();
JsonObject b = new JsonObject();
@@ -172,6 +190,7 @@ public class JsonObjectTest extends TestCase {
assertFalse(b.equals(a));
}
+ @Test
public void testEqualsHashCodeIgnoringOrder() {
JsonObject a = new JsonObject();
JsonObject b = new JsonObject();
@@ -188,6 +207,7 @@ public class JsonObjectTest extends TestCase {
MoreAsserts.assertEqualsAndHashCode(a, b);
}
+ @Test
public void testSize() {
JsonObject o = new JsonObject();
assertEquals(0, o.size());
@@ -202,6 +222,7 @@ public class JsonObjectTest extends TestCase {
assertEquals(1, o.size());
}
+ @Test
public void testDeepCopy() {
JsonObject original = new JsonObject();
JsonArray firstEntry = new JsonArray();
@@ -217,6 +238,7 @@ public class JsonObjectTest extends TestCase {
/**
* From issue 941
*/
+ @Test
public void testKeySet() {
JsonObject a = new JsonObject();
assertEquals(0, a.keySet().size());
@@ -250,6 +272,7 @@ public class JsonObjectTest extends TestCase {
}
}
+ @Test
public void testEntrySet() {
JsonObject o = new JsonObject();
assertEquals(0, o.entrySet().size());
diff --git a/gson/src/test/java/com/google/gson/MixedStreamTest.java b/gson/src/test/java/com/google/gson/MixedStreamTest.java
index 1d7c85c4..23ed6fa9 100644
--- a/gson/src/test/java/com/google/gson/MixedStreamTest.java
+++ b/gson/src/test/java/com/google/gson/MixedStreamTest.java
@@ -174,7 +174,7 @@ public final class MixedStreamTest extends TestCase {
} catch (NullPointerException expected) {
}
try {
- gson.fromJson(new JsonReader(new StringReader("true")), null);
+ gson.fromJson(new JsonReader(new StringReader("true")), (Type) null);
fail();
} catch (NullPointerException expected) {
}
diff --git a/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java
index d878850e..2b3fbafa 100644
--- a/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java
+++ b/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java
@@ -16,40 +16,82 @@
package com.google.gson;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import com.google.gson.annotations.Since;
+import com.google.gson.annotations.Until;
import com.google.gson.internal.Excluder;
-import junit.framework.TestCase;
+import org.junit.Test;
/**
* Unit tests for the {@link Excluder} class.
*
* @author Joel Leitch
*/
-public class VersionExclusionStrategyTest extends TestCase {
+public class VersionExclusionStrategyTest {
private static final double VERSION = 5.0D;
- public void testClassAndFieldAreAtSameVersion() throws Exception {
+ @Test
+ public void testSameVersion() throws Exception {
Excluder excluder = Excluder.DEFAULT.withVersion(VERSION);
- assertFalse(excluder.excludeClass(MockObject.class, true));
- assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true));
+ assertFalse(excluder.excludeClass(MockClassSince.class, true));
+ assertFalse(excluder.excludeField(MockClassSince.class.getField("someField"), true));
+
+ // Until version is exclusive
+ assertTrue(excluder.excludeClass(MockClassUntil.class, true));
+ assertTrue(excluder.excludeField(MockClassUntil.class.getField("someField"), true));
+
+ assertFalse(excluder.excludeClass(MockClassBoth.class, true));
+ assertFalse(excluder.excludeField(MockClassBoth.class.getField("someField"), true));
}
- public void testClassAndFieldAreBehindInVersion() throws Exception {
- Excluder excluder = Excluder.DEFAULT.withVersion(VERSION + 1);
- assertFalse(excluder.excludeClass(MockObject.class, true));
- assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true));
+ @Test
+ public void testNewerVersion() throws Exception {
+ Excluder excluder = Excluder.DEFAULT.withVersion(VERSION + 5);
+ assertFalse(excluder.excludeClass(MockClassSince.class, true));
+ assertFalse(excluder.excludeField(MockClassSince.class.getField("someField"), true));
+
+ assertTrue(excluder.excludeClass(MockClassUntil.class, true));
+ assertTrue(excluder.excludeField(MockClassUntil.class.getField("someField"), true));
+
+ assertTrue(excluder.excludeClass(MockClassBoth.class, true));
+ assertTrue(excluder.excludeField(MockClassBoth.class.getField("someField"), true));
}
- public void testClassAndFieldAreAheadInVersion() throws Exception {
- Excluder excluder = Excluder.DEFAULT.withVersion(VERSION - 1);
- assertTrue(excluder.excludeClass(MockObject.class, true));
- assertTrue(excluder.excludeField(MockObject.class.getField("someField"), true));
+ @Test
+ public void testOlderVersion() throws Exception {
+ Excluder excluder = Excluder.DEFAULT.withVersion(VERSION - 5);
+ assertTrue(excluder.excludeClass(MockClassSince.class, true));
+ assertTrue(excluder.excludeField(MockClassSince.class.getField("someField"), true));
+
+ assertFalse(excluder.excludeClass(MockClassUntil.class, true));
+ assertFalse(excluder.excludeField(MockClassUntil.class.getField("someField"), true));
+
+ assertTrue(excluder.excludeClass(MockClassBoth.class, true));
+ assertTrue(excluder.excludeField(MockClassBoth.class.getField("someField"), true));
}
@Since(VERSION)
- private static class MockObject {
+ private static class MockClassSince {
@Since(VERSION)
public final int someField = 0;
}
+
+ @Until(VERSION)
+ private static class MockClassUntil {
+
+ @Until(VERSION)
+ public final int someField = 0;
+ }
+
+ @Since(VERSION)
+ @Until(VERSION + 2)
+ private static class MockClassBoth {
+
+ @Since(VERSION)
+ @Until(VERSION + 2)
+ public final int someField = 0;
+ }
}
diff --git a/gson/src/test/java/com/google/gson/functional/GsonVersionDiagnosticsTest.java b/gson/src/test/java/com/google/gson/functional/GsonVersionDiagnosticsTest.java
index aa6f4ccb..daa7aa48 100644
--- a/gson/src/test/java/com/google/gson/functional/GsonVersionDiagnosticsTest.java
+++ b/gson/src/test/java/com/google/gson/functional/GsonVersionDiagnosticsTest.java
@@ -35,7 +35,7 @@ import junit.framework.TestCase;
* @author Inderjeet Singh
*/
public class GsonVersionDiagnosticsTest extends TestCase {
- private static final Pattern GSON_VERSION_PATTERN = Pattern.compile("(\\(GSON \\d\\.\\d\\.\\d)(?:[-.][A-Z]+)?\\)$");
+ private static final Pattern GSON_VERSION_PATTERN = Pattern.compile("(\\(GSON \\d\\.\\d+(\\.\\d)?)(?:[-.][A-Z]+)?\\)$");
private Gson gson;
diff --git a/gson/src/test/java/com/google/gson/functional/Java17RecordTest.java b/gson/src/test/java/com/google/gson/functional/Java17RecordTest.java
new file mode 100644
index 00000000..5e050eed
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/Java17RecordTest.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2022 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.ReflectionAccessFilter.FilterResult;
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class Java17RecordTest {
+ private final Gson gson = new Gson();
+
+ @Test
+ public void testFirstNameIsChosenForSerialization() {
+ RecordWithCustomNames target = new RecordWithCustomNames("v1", "v2");
+ // Ensure name1 occurs exactly once, and name2 and name3 don't appear
+ assertEquals("{\"name\":\"v1\",\"name1\":\"v2\"}", gson.toJson(target));
+ }
+
+ @Test
+ public void testMultipleNamesDeserializedCorrectly() {
+ assertEquals("v1", gson.fromJson("{'name':'v1'}", RecordWithCustomNames.class).a);
+
+ // Both name1 and name2 gets deserialized to b
+ assertEquals("v11", gson.fromJson("{'name': 'v1', 'name1':'v11'}", RecordWithCustomNames.class).b);
+ assertEquals("v2", gson.fromJson("{'name': 'v1', 'name2':'v2'}", RecordWithCustomNames.class).b);
+ assertEquals("v3", gson.fromJson("{'name': 'v1', 'name3':'v3'}", RecordWithCustomNames.class).b);
+ }
+
+ @Test
+ public void testMultipleNamesInTheSameString() {
+ // The last value takes precedence
+ assertEquals("v3",
+ gson.fromJson("{'name': 'foo', 'name1':'v1','name2':'v2','name3':'v3'}", RecordWithCustomNames.class).b);
+ }
+
+ private record RecordWithCustomNames(
+ @SerializedName("name") String a,
+ @SerializedName(value = "name1", alternate = {"name2", "name3"}) String b) {}
+
+ @Test
+ public void testSerializedNameOnAccessor() {
+ record LocalRecord(int i) {
+ @SerializedName("a")
+ @Override
+ public int i() {
+ return i;
+ }
+ }
+
+ var exception = assertThrows(JsonIOException.class, () -> gson.getAdapter(LocalRecord.class));
+ assertEquals("@SerializedName on method '" + LocalRecord.class.getName() + "#i()' is not supported",
+ exception.getMessage());
+ }
+
+ @Test
+ public void testFieldNamingStrategy() {
+ record LocalRecord(int i) {}
+
+ Gson gson = new GsonBuilder()
+ .setFieldNamingStrategy(f -> f.getName() + "-custom")
+ .create();
+
+ assertEquals("{\"i-custom\":1}", gson.toJson(new LocalRecord(1)));
+ assertEquals(new LocalRecord(2), gson.fromJson("{\"i-custom\":2}", LocalRecord.class));
+ }
+
+ @Test
+ public void testUnknownJsonProperty() {
+ record LocalRecord(int i) {}
+
+ // Unknown property 'x' should be ignored
+ assertEquals(new LocalRecord(1), gson.fromJson("{\"i\":1,\"x\":2}", LocalRecord.class));
+ }
+
+ @Test
+ public void testDuplicateJsonProperties() {
+ record LocalRecord(Integer a, Integer b) {}
+
+ String json = "{\"a\":null,\"a\":2,\"b\":1,\"b\":null}";
+ // Should use value of last occurrence
+ assertEquals(new LocalRecord(2, null), gson.fromJson(json, LocalRecord.class));
+ }
+
+ @Test
+ public void testConstructorRuns() {
+ record LocalRecord(String s) {
+ LocalRecord {
+ s = "custom-" + s;
+ }
+ }
+
+ LocalRecord deserialized = gson.fromJson("{\"s\": null}", LocalRecord.class);
+ assertEquals(new LocalRecord(null), deserialized);
+ assertEquals("custom-null", deserialized.s());
+ }
+
+ /** Tests behavior when the canonical constructor throws an exception */
+ @Test
+ public void testThrowingConstructor() {
+ record LocalRecord(String s) {
+ static final RuntimeException thrownException = new RuntimeException("Custom exception");
+
+ @SuppressWarnings("unused")
+ LocalRecord {
+ throw thrownException;
+ }
+ }
+
+ try {
+ gson.fromJson("{\"s\":\"value\"}", LocalRecord.class);
+ fail();
+ }
+ // TODO: Adjust this once Gson throws more specific exception type
+ catch (RuntimeException e) {
+ assertEquals("Failed to invoke constructor '" + LocalRecord.class.getName() + "(String)' with args [value]",
+ e.getMessage());
+ assertSame(LocalRecord.thrownException, e.getCause());
+ }
+ }
+
+ @Test
+ public void testAccessorIsCalled() {
+ record LocalRecord(String s) {
+ @Override
+ public String s() {
+ return "accessor-value";
+ }
+ }
+
+ assertEquals("{\"s\":\"accessor-value\"}", gson.toJson(new LocalRecord(null)));
+ }
+
+ /** Tests behavior when a record accessor method throws an exception */
+ @Test
+ public void testThrowingAccessor() {
+ record LocalRecord(String s) {
+ static final RuntimeException thrownException = new RuntimeException("Custom exception");
+
+ @Override
+ public String s() {
+ throw thrownException;
+ }
+ }
+
+ try {
+ gson.toJson(new LocalRecord("a"));
+ fail();
+ } catch (JsonIOException e) {
+ assertEquals("Accessor method '" + LocalRecord.class.getName() + "#s()' threw exception",
+ e.getMessage());
+ assertSame(LocalRecord.thrownException, e.getCause());
+ }
+ }
+
+ /** Tests behavior for a record without components */
+ @Test
+ public void testEmptyRecord() {
+ record EmptyRecord() {}
+
+ assertEquals("{}", gson.toJson(new EmptyRecord()));
+ assertEquals(new EmptyRecord(), gson.fromJson("{}", EmptyRecord.class));
+ }
+
+ /**
+ * Tests behavior when {@code null} is serialized / deserialized as record value;
+ * basically makes sure the adapter is 'null-safe'
+ */
+ @Test
+ public void testRecordNull() throws IOException {
+ record LocalRecord(int i) {}
+
+ TypeAdapter adapter = gson.getAdapter(LocalRecord.class);
+ assertEquals("null", adapter.toJson(null));
+ assertNull(adapter.fromJson("null"));
+ }
+
+ @Test
+ public void testPrimitiveDefaultValues() {
+ RecordWithPrimitives expected = new RecordWithPrimitives("s", (byte) 0, (short) 0, 0, 0, 0, 0, '\0', false);
+ assertEquals(expected, gson.fromJson("{'aString': 's'}", RecordWithPrimitives.class));
+ }
+
+ @Test
+ public void testPrimitiveJsonNullValue() {
+ String s = "{'aString': 's', 'aByte': null, 'aShort': 0}";
+ var e = assertThrows(JsonParseException.class, () -> gson.fromJson(s, RecordWithPrimitives.class));
+ assertEquals("null is not allowed as value for record component 'aByte' of primitive type; at path $.aByte",
+ e.getMessage());
+ }
+
+ /**
+ * Tests behavior when JSON contains non-null value, but custom adapter returns null
+ * for primitive component
+ */
+ @Test
+ public void testPrimitiveAdapterNullValue() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(byte.class, new TypeAdapter() {
+ @Override public Byte read(JsonReader in) throws IOException {
+ in.skipValue();
+ // Always return null
+ return null;
+ }
+
+ @Override public void write(JsonWriter out, Byte value) {
+ throw new AssertionError("not needed for test");
+ }
+ })
+ .setLenient()
+ .create();
+
+ String s = "{'aString': 's', 'aByte': 0}";
+ var exception = assertThrows(JsonParseException.class, () -> gson.fromJson(s, RecordWithPrimitives.class));
+ assertEquals("null is not allowed as value for record component 'aByte' of primitive type; at path $.aByte",
+ exception.getMessage());
+ }
+
+ private record RecordWithPrimitives(
+ String aString, byte aByte, short aShort, int anInt, long aLong, float aFloat, double aDouble, char aChar, boolean aBoolean) {}
+
+ /** Tests behavior when value of Object component is missing; should default to null */
+ @Test
+ public void testObjectDefaultValue() {
+ record LocalRecord(String s, int i) {}
+
+ assertEquals(new LocalRecord(null, 1), gson.fromJson("{\"i\":1}", LocalRecord.class));
+ }
+
+ /**
+ * Tests serialization of a record with {@code static} field.
+ *
+ * Important: It is not documented that this is officially supported; this
+ * test just checks the current behavior.
+ */
+ @Test
+ public void testStaticFieldSerialization() {
+ // By default Gson should ignore static fields
+ assertEquals("{}", gson.toJson(new RecordWithStaticField()));
+
+ Gson gson = new GsonBuilder()
+ // Include static fields
+ .excludeFieldsWithModifiers(0)
+ .create();
+
+ String json = gson.toJson(new RecordWithStaticField());
+ assertEquals("{\"s\":\"initial\"}", json);
+ }
+
+ /**
+ * Tests deserialization of a record with {@code static} field.
+ *
+ *
Important: It is not documented that this is officially supported; this
+ * test just checks the current behavior.
+ */
+ @Test
+ public void testStaticFieldDeserialization() {
+ // By default Gson should ignore static fields
+ gson.fromJson("{\"s\":\"custom\"}", RecordWithStaticField.class);
+ assertEquals("initial", RecordWithStaticField.s);
+
+ Gson gson = new GsonBuilder()
+ // Include static fields
+ .excludeFieldsWithModifiers(0)
+ .create();
+
+ String oldValue = RecordWithStaticField.s;
+ try {
+ RecordWithStaticField obj = gson.fromJson("{\"s\":\"custom\"}", RecordWithStaticField.class);
+ assertNotNull(obj);
+ // Currently record deserialization always ignores static fields
+ assertEquals("initial", RecordWithStaticField.s);
+ } finally {
+ RecordWithStaticField.s = oldValue;
+ }
+ }
+
+ private record RecordWithStaticField() {
+ static String s = "initial";
+ }
+
+ @Test
+ public void testExposeAnnotation() {
+ record RecordWithExpose(
+ @Expose int a,
+ int b
+ ) {}
+
+ Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
+ String json = gson.toJson(new RecordWithExpose(1, 2));
+ assertEquals("{\"a\":1}", json);
+ }
+
+ @Test
+ public void testFieldExclusionStrategy() {
+ record LocalRecord(int a, int b, double c) {}
+
+ Gson gson = new GsonBuilder()
+ .setExclusionStrategies(new ExclusionStrategy() {
+ @Override public boolean shouldSkipField(FieldAttributes f) {
+ return f.getName().equals("a");
+ }
+
+ @Override public boolean shouldSkipClass(Class> clazz) {
+ return clazz == double.class;
+ }
+ })
+ .create();
+
+ assertEquals("{\"b\":2}", gson.toJson(new LocalRecord(1, 2, 3.0)));
+ }
+
+ @Test
+ public void testJsonAdapterAnnotation() {
+ record Adapter() implements JsonSerializer, JsonDeserializer {
+ @Override public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ return "deserializer-" + json.getAsString();
+ }
+
+ @Override public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive("serializer-" + src);
+ }
+ }
+ record LocalRecord(
+ @JsonAdapter(Adapter.class) String s
+ ) {}
+
+ assertEquals("{\"s\":\"serializer-a\"}", gson.toJson(new LocalRecord("a")));
+ assertEquals(new LocalRecord("deserializer-a"), gson.fromJson("{\"s\":\"a\"}", LocalRecord.class));
+ }
+
+ @Test
+ public void testClassReflectionFilter() {
+ record Allowed(int a) {}
+ record Blocked(int b) {}
+
+ Gson gson = new GsonBuilder()
+ .addReflectionAccessFilter(c -> c == Allowed.class ? FilterResult.ALLOW : FilterResult.BLOCK_ALL)
+ .create();
+
+ String json = gson.toJson(new Allowed(1));
+ assertEquals("{\"a\":1}", json);
+
+ var exception = assertThrows(JsonIOException.class, () -> gson.toJson(new Blocked(1)));
+ assertEquals("ReflectionAccessFilter does not permit using reflection for class " + Blocked.class.getName() +
+ ". Register a TypeAdapter for this type or adjust the access filter.",
+ exception.getMessage());
+ }
+
+ @Test
+ public void testReflectionFilterBlockInaccessible() {
+ Gson gson = new GsonBuilder()
+ .addReflectionAccessFilter(c -> FilterResult.BLOCK_INACCESSIBLE)
+ .create();
+
+ var exception = assertThrows(JsonIOException.class, () -> gson.toJson(new PrivateRecord(1)));
+ assertEquals("Constructor 'com.google.gson.functional.Java17RecordTest$PrivateRecord(int)' is not accessible and"
+ + " ReflectionAccessFilter does not permit making it accessible. Register a TypeAdapter for the declaring"
+ + " type, adjust the access filter or increase the visibility of the element and its declaring type.",
+ exception.getMessage());
+
+ exception = assertThrows(JsonIOException.class, () -> gson.fromJson("{}", PrivateRecord.class));
+ assertEquals("Constructor 'com.google.gson.functional.Java17RecordTest$PrivateRecord(int)' is not accessible and"
+ + " ReflectionAccessFilter does not permit making it accessible. Register a TypeAdapter for the declaring"
+ + " type, adjust the access filter or increase the visibility of the element and its declaring type.",
+ exception.getMessage());
+
+ assertEquals("{\"i\":1}", gson.toJson(new PublicRecord(1)));
+ assertEquals(new PublicRecord(2), gson.fromJson("{\"i\":2}", PublicRecord.class));
+ }
+
+ private record PrivateRecord(int i) {}
+ public record PublicRecord(int i) {}
+
+ /**
+ * Tests behavior when {@code java.lang.Record} is used as type for serialization
+ * and deserialization.
+ */
+ @Test
+ public void testRecordBaseClass() {
+ record LocalRecord(int i) {}
+
+ assertEquals("{}", gson.toJson(new LocalRecord(1), Record.class));
+
+ var exception = assertThrows(JsonIOException.class, () -> gson.fromJson("{}", Record.class));
+ assertEquals("Abstract classes can't be instantiated! Register an InstanceCreator or a TypeAdapter for"
+ + " this type. Class name: java.lang.Record",
+ exception.getMessage());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/JsonArrayTest.java b/gson/src/test/java/com/google/gson/functional/JsonArrayTest.java
deleted file mode 100644
index 410a0817..00000000
--- a/gson/src/test/java/com/google/gson/functional/JsonArrayTest.java
+++ /dev/null
@@ -1,161 +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 com.google.gson.JsonArray;
-import java.math.BigInteger;
-import junit.framework.TestCase;
-
-/**
- * Functional tests for adding primitives to a JsonArray.
- *
- * @author Dillon Dixon
- */
-public class JsonArrayTest extends TestCase {
-
- public void testStringPrimitiveAddition() {
- JsonArray jsonArray = new JsonArray();
-
- jsonArray.add("Hello");
- jsonArray.add("Goodbye");
- jsonArray.add("Thank you");
- jsonArray.add((String) null);
- jsonArray.add("Yes");
-
- assertEquals("[\"Hello\",\"Goodbye\",\"Thank you\",null,\"Yes\"]", jsonArray.toString());
- }
-
- public void testIntegerPrimitiveAddition() {
- JsonArray jsonArray = new JsonArray();
-
- int x = 1;
- jsonArray.add(x);
-
- x = 2;
- jsonArray.add(x);
-
- x = -3;
- jsonArray.add(x);
-
- jsonArray.add((Integer) null);
-
- x = 4;
- jsonArray.add(x);
-
- x = 0;
- jsonArray.add(x);
-
- assertEquals("[1,2,-3,null,4,0]", jsonArray.toString());
- }
-
- public void testDoublePrimitiveAddition() {
- JsonArray jsonArray = new JsonArray();
-
- double x = 1.0;
- jsonArray.add(x);
-
- x = 2.13232;
- jsonArray.add(x);
-
- x = 0.121;
- jsonArray.add(x);
-
- jsonArray.add((Double) null);
-
- x = -0.00234;
- jsonArray.add(x);
-
- jsonArray.add((Double) null);
-
- assertEquals("[1.0,2.13232,0.121,null,-0.00234,null]", jsonArray.toString());
- }
-
- public void testBooleanPrimitiveAddition() {
- JsonArray jsonArray = new JsonArray();
-
- jsonArray.add(true);
- jsonArray.add(true);
- jsonArray.add(false);
- jsonArray.add(false);
- jsonArray.add((Boolean) null);
- jsonArray.add(true);
-
- assertEquals("[true,true,false,false,null,true]", jsonArray.toString());
- }
-
- public void testCharPrimitiveAddition() {
- JsonArray jsonArray = new JsonArray();
-
- jsonArray.add('a');
- jsonArray.add('e');
- jsonArray.add('i');
- jsonArray.add((char) 111);
- jsonArray.add((Character) null);
- jsonArray.add('u');
- jsonArray.add("and sometimes Y");
-
- assertEquals("[\"a\",\"e\",\"i\",\"o\",null,\"u\",\"and sometimes Y\"]", jsonArray.toString());
- }
-
- public void testMixedPrimitiveAddition() {
- JsonArray jsonArray = new JsonArray();
-
- jsonArray.add('a');
- jsonArray.add("apple");
- jsonArray.add(12121);
- jsonArray.add((char) 111);
- jsonArray.add((Boolean) null);
- jsonArray.add((Character) null);
- jsonArray.add(12.232);
- jsonArray.add(BigInteger.valueOf(2323));
-
- assertEquals("[\"a\",\"apple\",12121,\"o\",null,null,12.232,2323]", jsonArray.toString());
- }
-
- public void testNullPrimitiveAddition() {
- JsonArray jsonArray = new JsonArray();
-
- jsonArray.add((Character) null);
- jsonArray.add((Boolean) null);
- jsonArray.add((Integer) null);
- jsonArray.add((Double) null);
- jsonArray.add((Float) null);
- jsonArray.add((BigInteger) null);
- jsonArray.add((String) null);
- jsonArray.add((Boolean) null);
- jsonArray.add((Number) null);
-
- assertEquals("[null,null,null,null,null,null,null,null,null]", jsonArray.toString());
- }
-
- public void testSameAddition() {
- JsonArray jsonArray = new JsonArray();
-
- jsonArray.add('a');
- jsonArray.add('a');
- jsonArray.add(true);
- jsonArray.add(true);
- jsonArray.add(1212);
- jsonArray.add(1212);
- jsonArray.add(34.34);
- jsonArray.add(34.34);
- jsonArray.add((Boolean) null);
- jsonArray.add((Boolean) null);
-
- assertEquals("[\"a\",\"a\",true,true,1212,1212,34.34,34.34,null,null]", jsonArray.toString());
- }
-}
diff --git a/gson/src/test/java/com/google/gson/functional/ObjectTest.java b/gson/src/test/java/com/google/gson/functional/ObjectTest.java
index 0bcbc5d2..9fd5dd50 100644
--- a/gson/src/test/java/com/google/gson/functional/ObjectTest.java
+++ b/gson/src/test/java/com/google/gson/functional/ObjectTest.java
@@ -20,6 +20,7 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
@@ -482,6 +483,16 @@ public class ObjectTest extends TestCase {
gson.fromJson(gson.toJson(product), Product.class);
}
+ static final class Department {
+ public String name = "abc";
+ public String code = "123";
+ }
+
+ static final class Product {
+ private List attributes = new ArrayList<>();
+ private List departments = new ArrayList<>();
+ }
+
// http://code.google.com/p/google-gson/issues/detail?id=270
public void testDateAsMapObjectField() {
HasObjectMap a = new HasObjectMap();
@@ -493,17 +504,92 @@ public class ObjectTest extends TestCase {
}
}
- public class HasObjectMap {
+ static class HasObjectMap {
Map map = new HashMap<>();
}
- static final class Department {
- public String name = "abc";
- public String code = "123";
+ /**
+ * Tests serialization of a class with {@code static} field.
+ *
+ * Important: It is not documented that this is officially supported; this
+ * test just checks the current behavior.
+ */
+ public void testStaticFieldSerialization() {
+ // By default Gson should ignore static fields
+ assertEquals("{}", gson.toJson(new ClassWithStaticField()));
+
+ Gson gson = new GsonBuilder()
+ // Include static fields
+ .excludeFieldsWithModifiers(0)
+ .create();
+
+ String json = gson.toJson(new ClassWithStaticField());
+ assertEquals("{\"s\":\"initial\"}", json);
+
+ json = gson.toJson(new ClassWithStaticFinalField());
+ assertEquals("{\"s\":\"initial\"}", json);
}
- static final class Product {
- private List attributes = new ArrayList<>();
- private List departments = new ArrayList<>();
+ /**
+ * Tests deserialization of a class with {@code static} field.
+ *
+ * Important: It is not documented that this is officially supported; this
+ * test just checks the current behavior.
+ */
+ public void testStaticFieldDeserialization() {
+ // By default Gson should ignore static fields
+ gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticField.class);
+ assertEquals("initial", ClassWithStaticField.s);
+
+ Gson gson = new GsonBuilder()
+ // Include static fields
+ .excludeFieldsWithModifiers(0)
+ .create();
+
+ String oldValue = ClassWithStaticField.s;
+ try {
+ ClassWithStaticField obj = gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticField.class);
+ assertNotNull(obj);
+ assertEquals("custom", ClassWithStaticField.s);
+ } finally {
+ ClassWithStaticField.s = oldValue;
+ }
+
+ try {
+ gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticFinalField.class);
+ fail();
+ } catch (JsonIOException e) {
+ assertEquals("Cannot set value of 'static final' field 'com.google.gson.functional.ObjectTest$ClassWithStaticFinalField#s'",
+ e.getMessage());
+ }
+ }
+
+ static class ClassWithStaticField {
+ static String s = "initial";
+ }
+
+ static class ClassWithStaticFinalField {
+ static final String s = "initial";
+ }
+
+ public void testThrowingDefaultConstructor() {
+ try {
+ gson.fromJson("{}", ClassWithThrowingConstructor.class);
+ fail();
+ }
+ // TODO: Adjust this once Gson throws more specific exception type
+ catch (RuntimeException e) {
+ assertEquals("Failed to invoke constructor 'com.google.gson.functional.ObjectTest$ClassWithThrowingConstructor()' with no args",
+ e.getMessage());
+ assertSame(ClassWithThrowingConstructor.thrownException, e.getCause());
+ }
+ }
+
+ static class ClassWithThrowingConstructor {
+ static final RuntimeException thrownException = new RuntimeException("Custom exception");
+
+ public ClassWithThrowingConstructor() {
+ throw thrownException;
+ }
}
}
diff --git a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java
index 49cb0db9..e6168746 100644
--- a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java
+++ b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java
@@ -16,13 +16,19 @@
package com.google.gson.functional;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
import com.google.gson.ParameterizedTypeFixtures.MyParameterizedType;
import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeAdapter;
import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeInstanceCreator;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
@@ -32,7 +38,8 @@ import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
/**
* Functional tests for the serialization and deserialization of parameterized types in Gson.
@@ -40,15 +47,15 @@ import junit.framework.TestCase;
* @author Inderjeet Singh
* @author Joel Leitch
*/
-public class ParameterizedTypesTest extends TestCase {
+public class ParameterizedTypesTest {
private Gson gson;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() {
gson = new Gson();
}
+ @Test
public void testParameterizedTypesSerialization() throws Exception {
MyParameterizedType src = new MyParameterizedType<>(10);
Type typeOfSrc = new TypeToken>() {}.getType();
@@ -56,6 +63,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(src.getExpectedJson(), json);
}
+ @Test
public void testParameterizedTypeDeserialization() throws Exception {
BagOfPrimitives bag = new BagOfPrimitives();
MyParameterizedType expected = new MyParameterizedType<>(bag);
@@ -70,6 +78,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(expected, actual);
}
+ @Test
public void testTypesWithMultipleParametersSerialization() throws Exception {
MultiParameters src =
new MultiParameters<>(10, 1.0F, 2.1D, "abc", new BagOfPrimitives());
@@ -81,6 +90,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(expected, json);
}
+ @Test
public void testTypesWithMultipleParametersDeserialization() throws Exception {
Type typeOfTarget = new TypeToken>() {}.getType();
@@ -93,6 +103,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(expected, target);
}
+ @Test
public void testParameterizedTypeWithCustomSerializer() {
Type ptIntegerType = new TypeToken>() {}.getType();
Type ptStringType = new TypeToken>() {}.getType();
@@ -109,6 +120,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(MyParameterizedTypeAdapter.getExpectedJson(stringTarget), json);
}
+ @Test
public void testParameterizedTypesWithCustomDeserializer() {
Type ptIntegerType = new TypeToken>() {}.getType();
Type ptStringType = new TypeToken>() {}.getType();
@@ -130,6 +142,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals("abc", stringTarget.value);
}
+ @Test
public void testParameterizedTypesWithWriterSerialization() throws Exception {
Writer writer = new StringWriter();
MyParameterizedType src = new MyParameterizedType<>(10);
@@ -138,6 +151,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(src.getExpectedJson(), writer.toString());
}
+ @Test
public void testParameterizedTypeWithReaderDeserialization() throws Exception {
BagOfPrimitives bag = new BagOfPrimitives();
MyParameterizedType expected = new MyParameterizedType<>(bag);
@@ -158,6 +172,7 @@ public class ParameterizedTypesTest extends TestCase {
return args;
}
+ @Test
public void testVariableTypeFieldsAndGenericArraysSerialization() throws Exception {
Integer obj = 0;
Integer[] array = { 1, 2, 3 };
@@ -174,6 +189,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objToSerialize.getExpectedJson(), json);
}
+ @Test
public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Exception {
Integer obj = 0;
Integer[] array = { 1, 2, 3 };
@@ -191,6 +207,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}
+ @Test
public void testVariableTypeDeserialization() throws Exception {
Type typeOfSrc = new TypeToken>() {}.getType();
ObjectWithTypeVariables objToSerialize =
@@ -201,6 +218,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}
+ @Test
public void testVariableTypeArrayDeserialization() throws Exception {
Integer[] array = { 1, 2, 3 };
@@ -213,6 +231,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}
+ @Test
public void testParameterizedTypeWithVariableTypeDeserialization() throws Exception {
List list = new ArrayList<>();
list.add(4);
@@ -227,6 +246,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}
+ @Test
public void testParameterizedTypeGenericArraysSerialization() throws Exception {
List list = new ArrayList<>();
list.add(1);
@@ -240,6 +260,7 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals("{\"arrayOfListOfTypeParameters\":[[1,2],[1,2]]}", json);
}
+ @Test
public void testParameterizedTypeGenericArraysDeserialization() throws Exception {
List list = new ArrayList<>();
list.add(1);
@@ -483,6 +504,7 @@ public class ParameterizedTypesTest extends TestCase {
int value = 30;
}
+ @Test
public void testDeepParameterizedTypeSerialization() {
Amount amount = new Amount<>();
String json = gson.toJson(amount);
@@ -490,6 +512,7 @@ public class ParameterizedTypesTest extends TestCase {
assertTrue(json.contains("30"));
}
+ @Test
public void testDeepParameterizedTypeDeserialization() {
String json = "{value:30}";
Type type = new TypeToken>() {}.getType();
@@ -497,4 +520,47 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(30, amount.value);
}
// End: tests to reproduce issue 103
+
+ private static void assertCorrectlyDeserialized(Object object) {
+ @SuppressWarnings("unchecked")
+ List list = (List) object;
+ assertEquals(1, list.size());
+ assertEquals(4, list.get(0).q);
+ }
+
+ @Test
+ public void testGsonFromJsonTypeToken() {
+ TypeToken> typeToken = new TypeToken>() {};
+ Type type = typeToken.getType();
+
+ {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("q", 4);
+ JsonArray jsonArray = new JsonArray();
+ jsonArray.add(jsonObject);
+
+ assertCorrectlyDeserialized(gson.fromJson(jsonArray, typeToken));
+ assertCorrectlyDeserialized(gson.fromJson(jsonArray, type));
+ }
+
+ String json = "[{\"q\":4}]";
+
+ {
+ assertCorrectlyDeserialized(gson.fromJson(json, typeToken));
+ assertCorrectlyDeserialized(gson.fromJson(json, type));
+ }
+
+ {
+ assertCorrectlyDeserialized(gson.fromJson(new StringReader(json), typeToken));
+ assertCorrectlyDeserialized(gson.fromJson(new StringReader(json), type));
+ }
+
+ {
+ JsonReader reader = new JsonReader(new StringReader(json));
+ assertCorrectlyDeserialized(gson.fromJson(reader, typeToken));
+
+ reader = new JsonReader(new StringReader(json));
+ assertCorrectlyDeserialized(gson.fromJson(reader, type));
+ }
+ }
}
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 fc0f6985..13716346 100644
--- a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java
+++ b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java
@@ -60,6 +60,11 @@ public class PrimitiveTest extends TestCase {
public void testByteSerialization() {
assertEquals("1", gson.toJson(1, byte.class));
assertEquals("1", gson.toJson(1, Byte.class));
+ assertEquals(Byte.toString(Byte.MIN_VALUE), gson.toJson(Byte.MIN_VALUE, Byte.class));
+ assertEquals(Byte.toString(Byte.MAX_VALUE), gson.toJson(Byte.MAX_VALUE, Byte.class));
+ // Should perform narrowing conversion
+ assertEquals("-128", gson.toJson(128, Byte.class));
+ assertEquals("1", gson.toJson(1.5, Byte.class));
}
public void testByteDeserialization() {
@@ -98,6 +103,13 @@ public class PrimitiveTest extends TestCase {
public void testShortSerialization() {
assertEquals("1", gson.toJson(1, short.class));
assertEquals("1", gson.toJson(1, Short.class));
+ assertEquals(Short.toString(Short.MIN_VALUE), gson.toJson(Short.MIN_VALUE, Short.class));
+ assertEquals(Short.toString(Short.MAX_VALUE), gson.toJson(Short.MAX_VALUE, Short.class));
+ // Should perform widening conversion
+ assertEquals("1", gson.toJson((byte) 1, Short.class));
+ // Should perform narrowing conversion
+ assertEquals("-32768", gson.toJson(32768, Short.class));
+ assertEquals("1", gson.toJson(1.5, Short.class));
}
public void testShortDeserialization() {
@@ -133,6 +145,54 @@ public class PrimitiveTest extends TestCase {
}
}
+ public void testIntSerialization() {
+ assertEquals("1", gson.toJson(1, int.class));
+ assertEquals("1", gson.toJson(1, Integer.class));
+ assertEquals(Integer.toString(Integer.MIN_VALUE), gson.toJson(Integer.MIN_VALUE, Integer.class));
+ assertEquals(Integer.toString(Integer.MAX_VALUE), gson.toJson(Integer.MAX_VALUE, Integer.class));
+ // Should perform widening conversion
+ assertEquals("1", gson.toJson((byte) 1, Integer.class));
+ // Should perform narrowing conversion
+ assertEquals("-2147483648", gson.toJson(2147483648L, Integer.class));
+ assertEquals("1", gson.toJson(1.5, Integer.class));
+ }
+
+ public void testLongSerialization() {
+ assertEquals("1", gson.toJson(1L, long.class));
+ assertEquals("1", gson.toJson(1L, Long.class));
+ assertEquals(Long.toString(Long.MIN_VALUE), gson.toJson(Long.MIN_VALUE, Long.class));
+ assertEquals(Long.toString(Long.MAX_VALUE), gson.toJson(Long.MAX_VALUE, Long.class));
+ // Should perform widening conversion
+ assertEquals("1", gson.toJson((byte) 1, Long.class));
+ // Should perform narrowing conversion
+ assertEquals("1", gson.toJson(1.5, Long.class));
+ }
+
+ public void testFloatSerialization() {
+ assertEquals("1.5", gson.toJson(1.5f, float.class));
+ assertEquals("1.5", gson.toJson(1.5f, Float.class));
+ assertEquals(Float.toString(Float.MIN_VALUE), gson.toJson(Float.MIN_VALUE, Float.class));
+ assertEquals(Float.toString(Float.MAX_VALUE), gson.toJson(Float.MAX_VALUE, Float.class));
+ // Should perform widening conversion
+ assertEquals("1.0", gson.toJson((byte) 1, Float.class));
+ // (This widening conversion is actually lossy)
+ assertEquals(Float.toString(Long.MAX_VALUE - 10L), gson.toJson(Long.MAX_VALUE - 10L, Float.class));
+ // Should perform narrowing conversion
+ gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+ assertEquals("Infinity", gson.toJson(Double.MAX_VALUE, Float.class));
+ }
+
+ public void testDoubleSerialization() {
+ assertEquals("1.5", gson.toJson(1.5, double.class));
+ assertEquals("1.5", gson.toJson(1.5, Double.class));
+ assertEquals(Double.toString(Double.MIN_VALUE), gson.toJson(Double.MIN_VALUE, Double.class));
+ assertEquals(Double.toString(Double.MAX_VALUE), gson.toJson(Double.MAX_VALUE, Double.class));
+ // Should perform widening conversion
+ assertEquals("1.0", gson.toJson((byte) 1, Double.class));
+ // (This widening conversion is actually lossy)
+ assertEquals(Double.toString(Long.MAX_VALUE - 10L), gson.toJson(Long.MAX_VALUE - 10L, Double.class));
+ }
+
public void testPrimitiveIntegerAutoboxedInASingleElementArraySerialization() {
int target[] = {-9332};
assertEquals("[-9332]", gson.toJson(target));
diff --git a/gson/src/test/java/com/google/gson/functional/ReflectionAccessFilterTest.java b/gson/src/test/java/com/google/gson/functional/ReflectionAccessFilterTest.java
index b2541df3..6c9ab449 100644
--- a/gson/src/test/java/com/google/gson/functional/ReflectionAccessFilterTest.java
+++ b/gson/src/test/java/com/google/gson/functional/ReflectionAccessFilterTest.java
@@ -2,6 +2,7 @@ package com.google.gson.functional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeNotNull;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -20,6 +21,7 @@ import com.google.gson.stream.JsonWriter;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;
@@ -39,7 +41,7 @@ public class ReflectionAccessFilterTest {
}
@Test
- public void testBlockInaccessibleJava() {
+ public void testBlockInaccessibleJava() throws ReflectiveOperationException {
Gson gson = new GsonBuilder()
.addReflectionAccessFilter(ReflectionAccessFilter.BLOCK_INACCESSIBLE_JAVA)
.create();
@@ -51,16 +53,26 @@ public class ReflectionAccessFilterTest {
} catch (JsonIOException expected) {
// Note: This test is rather brittle and depends on the JDK implementation
assertEquals(
- "Field 'java.io.File#path' is not accessible and ReflectionAccessFilter does not permit "
- + "making it accessible. Register a TypeAdapter for the declaring type or adjust the access filter.",
+ "Field 'java.io.File#path' is not accessible and ReflectionAccessFilter does not permit"
+ + " making it accessible. Register a TypeAdapter for the declaring type, adjust the access"
+ + " filter or increase the visibility of the element and its declaring type.",
expected.getMessage()
);
}
- // But serialization should succeed for classes with only public fields
- // awt is unavailable and this should just work (tm)
-// String json = gson.toJson(new Point(1, 2));
-// assertEquals("{\"x\":1,\"y\":2}", json);
+
+ // But serialization should succeed for classes with only public fields.
+ // Not many JDK classes have mutable public fields, thank goodness, but java.awt.Point does.
+ Class> pointClass = null;
+ try {
+ pointClass = Class.forName("java.awt.Point");
+ } catch (ClassNotFoundException e) {
+ }
+ assumeNotNull(pointClass);
+ Constructor> pointConstructor = pointClass.getConstructor(int.class, int.class);
+ Object point = pointConstructor.newInstance(1, 2);
+ String json = gson.toJson(point);
+ assertEquals("{\"x\":1,\"y\":2}", json);
}
@Test
@@ -74,8 +86,9 @@ public class ReflectionAccessFilterTest {
fail("Expected exception; test needs to be run with Java >= 9");
} catch (JsonIOException expected) {
assertEquals(
- "Field 'java.io.Reader#lock' is not accessible and ReflectionAccessFilter does not permit "
- + "making it accessible. Register a TypeAdapter for the declaring type or adjust the access filter.",
+ "Field 'java.io.Reader#lock' is not accessible and ReflectionAccessFilter does not permit"
+ + " making it accessible. Register a TypeAdapter for the declaring type, adjust the access"
+ + " filter or increase the visibility of the element and its declaring type.",
expected.getMessage()
);
}
@@ -93,8 +106,8 @@ public class ReflectionAccessFilterTest {
fail();
} catch (JsonIOException expected) {
assertEquals(
- "ReflectionAccessFilter does not permit using reflection for class java.lang.Thread. "
- + "Register a TypeAdapter for this type or adjust the access filter.",
+ "ReflectionAccessFilter does not permit using reflection for class java.lang.Thread."
+ + " Register a TypeAdapter for this type or adjust the access filter.",
expected.getMessage()
);
}
@@ -111,9 +124,9 @@ public class ReflectionAccessFilterTest {
fail();
} catch (JsonIOException expected) {
assertEquals(
- "ReflectionAccessFilter does not permit using reflection for class java.io.Reader "
- + "(supertype of class com.google.gson.functional.ReflectionAccessFilterTest$ClassExtendingJdkClass). "
- + "Register a TypeAdapter for this type or adjust the access filter.",
+ "ReflectionAccessFilter does not permit using reflection for class java.io.Reader"
+ + " (supertype of class com.google.gson.functional.ReflectionAccessFilterTest$ClassExtendingJdkClass)."
+ + " Register a TypeAdapter for this type or adjust the access filter.",
expected.getMessage()
);
}
@@ -141,9 +154,10 @@ public class ReflectionAccessFilterTest {
fail("Expected exception; test needs to be run with Java >= 9");
} catch (JsonIOException expected) {
assertEquals(
- "Field 'com.google.gson.functional.ReflectionAccessFilterTest$ClassWithStaticField#i' "
- + "is not accessible and ReflectionAccessFilter does not permit making it accessible. "
- + "Register a TypeAdapter for the declaring type or adjust the access filter.",
+ "Field 'com.google.gson.functional.ReflectionAccessFilterTest$ClassWithStaticField#i'"
+ + " is not accessible and ReflectionAccessFilter does not permit making it accessible."
+ + " Register a TypeAdapter for the declaring type, adjust the access filter or increase"
+ + " the visibility of the element and its declaring type.",
expected.getMessage()
);
}
@@ -183,9 +197,9 @@ public class ReflectionAccessFilterTest {
fail();
} catch (JsonIOException expected) {
assertEquals(
- "ReflectionAccessFilter does not permit using reflection for class "
- + "com.google.gson.functional.ReflectionAccessFilterTest$SuperTestClass. "
- + "Register a TypeAdapter for this type or adjust the access filter.",
+ "ReflectionAccessFilter does not permit using reflection for class"
+ + " com.google.gson.functional.ReflectionAccessFilterTest$SuperTestClass."
+ + " Register a TypeAdapter for this type or adjust the access filter.",
expected.getMessage()
);
}
@@ -222,9 +236,10 @@ public class ReflectionAccessFilterTest {
fail("Expected exception; test needs to be run with Java >= 9");
} catch (JsonIOException expected) {
assertEquals(
- "Field 'com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateField#i' "
- + "is not accessible and ReflectionAccessFilter does not permit making it accessible. "
- + "Register a TypeAdapter for the declaring type or adjust the access filter.",
+ "Field 'com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateField#i'"
+ + " is not accessible and ReflectionAccessFilter does not permit making it accessible."
+ + " Register a TypeAdapter for the declaring type, adjust the access filter or increase"
+ + " the visibility of the element and its declaring type.",
expected.getMessage()
);
}
@@ -263,9 +278,9 @@ public class ReflectionAccessFilterTest {
fail("Expected exception; test needs to be run with Java >= 9");
} catch (JsonIOException expected) {
assertEquals(
- "Unable to invoke no-args constructor of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateNoArgsConstructor; "
- + "constructor is not accessible and ReflectionAccessFilter does not permit making it accessible. Register an "
- + "InstanceCreator or a TypeAdapter for this type, change the visibility of the constructor or adjust the access filter.",
+ "Unable to invoke no-args constructor of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateNoArgsConstructor;"
+ + " constructor is not accessible and ReflectionAccessFilter does not permit making it accessible. Register an"
+ + " InstanceCreator or a TypeAdapter for this type, change the visibility of the constructor or adjust the access filter.",
expected.getMessage()
);
}
@@ -295,9 +310,9 @@ public class ReflectionAccessFilterTest {
fail();
} catch (JsonIOException expected) {
assertEquals(
- "Unable to create instance of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithoutNoArgsConstructor; "
- + "ReflectionAccessFilter does not permit using reflection or Unsafe. Register an InstanceCreator "
- + "or a TypeAdapter for this type or adjust the access filter to allow using reflection.",
+ "Unable to create instance of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithoutNoArgsConstructor;"
+ + " ReflectionAccessFilter does not permit using reflection or Unsafe. Register an InstanceCreator"
+ + " or a TypeAdapter for this type or adjust the access filter to allow using reflection.",
expected.getMessage()
);
}
@@ -311,7 +326,7 @@ public class ReflectionAccessFilterTest {
}
@Override public void write(JsonWriter out, ClassWithoutNoArgsConstructor value) throws IOException {
throw new AssertionError("Not needed for test");
- };
+ }
})
.create();
ClassWithoutNoArgsConstructor deserialized = gson.fromJson("{}", ClassWithoutNoArgsConstructor.class);
@@ -357,8 +372,8 @@ public class ReflectionAccessFilterTest {
fail();
} catch (JsonIOException expected) {
assertEquals(
- "ReflectionAccessFilter does not permit using reflection for class com.google.gson.functional.ReflectionAccessFilterTest$OtherClass. "
- + "Register a TypeAdapter for this type or adjust the access filter.",
+ "ReflectionAccessFilter does not permit using reflection for class com.google.gson.functional.ReflectionAccessFilterTest$OtherClass."
+ + " Register a TypeAdapter for this type or adjust the access filter.",
expected.getMessage()
);
}
@@ -417,8 +432,8 @@ public class ReflectionAccessFilterTest {
fail();
} catch (JsonIOException expected) {
assertEquals(
- "Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for "
- + "this type. Interface name: java.lang.Runnable",
+ "Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for"
+ + " this type. Interface name: java.lang.Runnable",
expected.getMessage()
);
}
diff --git a/gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java b/gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java
index ce2458cb..3b485581 100644
--- a/gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java
+++ b/gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java
@@ -56,8 +56,8 @@ public class ReflectionAccessTest {
fail("Unexpected exception; test has to be run with `--illegal-access=deny`");
} catch (JsonIOException expected) {
assertTrue(expected.getMessage().startsWith(
- "Failed making constructor 'java.util.Collections$EmptyList#EmptyList()' accessible; "
- + "either change its visibility or write a custom InstanceCreator or TypeAdapter for its declaring type"
+ "Failed making constructor 'java.util.Collections$EmptyList()' accessible;"
+ + " either increase its visibility or write a custom InstanceCreator or TypeAdapter for its declaring type: "
));
}
}
diff --git a/gson/src/test/java/com/google/gson/functional/TypeAdapterRuntimeTypeWrapperTest.java b/gson/src/test/java/com/google/gson/functional/TypeAdapterRuntimeTypeWrapperTest.java
new file mode 100644
index 00000000..73a01012
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/TypeAdapterRuntimeTypeWrapperTest.java
@@ -0,0 +1,193 @@
+package com.google.gson.functional;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import org.junit.Test;
+
+public class TypeAdapterRuntimeTypeWrapperTest {
+ private static class Base {
+ }
+ private static class Subclass extends Base {
+ @SuppressWarnings("unused")
+ String f = "test";
+ }
+ private static class Container {
+ @SuppressWarnings("unused")
+ Base b = new Subclass();
+ }
+ private static class Deserializer implements JsonDeserializer {
+ @Override
+ public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ throw new AssertionError("not needed for this test");
+ }
+ }
+
+ /**
+ * When custom {@link JsonSerializer} is registered for Base should
+ * prefer that over reflective adapter for Subclass for serialization.
+ */
+ @Test
+ public void testJsonSerializer() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new JsonSerializer () {
+ @Override
+ public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive("serializer");
+ }
+ })
+ .create();
+
+ String json = gson.toJson(new Container());
+ assertEquals("{\"b\":\"serializer\"}", json);
+ }
+
+ /**
+ * When only {@link JsonDeserializer} is registered for Base, then on
+ * serialization should prefer reflective adapter for Subclass since
+ * Base would use reflective adapter as delegate.
+ */
+ @Test
+ public void testJsonDeserializer_ReflectiveSerializerDelegate() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new Deserializer())
+ .create();
+
+ String json = gson.toJson(new Container());
+ assertEquals("{\"b\":{\"f\":\"test\"}}", json);
+ }
+
+ /**
+ * When {@link JsonDeserializer} with custom adapter as delegate is
+ * registered for Base, then on serialization should prefer custom adapter
+ * delegate for Base over reflective adapter for Subclass.
+ */
+ @Test
+ public void testJsonDeserializer_CustomSerializerDelegate() {
+ Gson gson = new GsonBuilder()
+ // Register custom delegate
+ .registerTypeAdapter(Base.class, new TypeAdapter () {
+ @Override
+ public Base read(JsonReader in) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public void write(JsonWriter out, Base value) throws IOException {
+ out.value("custom delegate");
+ }
+ })
+ .registerTypeAdapter(Base.class, new Deserializer())
+ .create();
+
+ String json = gson.toJson(new Container());
+ assertEquals("{\"b\":\"custom delegate\"}", json);
+ }
+
+ /**
+ * When two (or more) {@link JsonDeserializer}s are registered for Base
+ * which eventually fall back to reflective adapter as delegate, then on
+ * serialization should prefer reflective adapter for Subclass.
+ */
+ @Test
+ public void testJsonDeserializer_ReflectiveTreeSerializerDelegate() {
+ Gson gson = new GsonBuilder()
+ // Register delegate which itself falls back to reflective serialization
+ .registerTypeAdapter(Base.class, new Deserializer())
+ .registerTypeAdapter(Base.class, new Deserializer())
+ .create();
+
+ String json = gson.toJson(new Container());
+ assertEquals("{\"b\":{\"f\":\"test\"}}", json);
+ }
+
+ /**
+ * When {@link JsonDeserializer} with {@link JsonSerializer} as delegate
+ * is registered for Base, then on serialization should prefer
+ * {@code JsonSerializer} over reflective adapter for Subclass.
+ */
+ @Test
+ public void testJsonDeserializer_JsonSerializerDelegate() {
+ Gson gson = new GsonBuilder()
+ // Register JsonSerializer as delegate
+ .registerTypeAdapter(Base.class, new JsonSerializer () {
+ @Override
+ public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive("custom delegate");
+ }
+ })
+ .registerTypeAdapter(Base.class, new Deserializer())
+ .create();
+
+ String json = gson.toJson(new Container());
+ assertEquals("{\"b\":\"custom delegate\"}", json);
+ }
+
+ /**
+ * When a {@link JsonDeserializer} is registered for Subclass, and a custom
+ * {@link JsonSerializer} is registered for Base, then Gson should prefer
+ * the reflective adapter for Subclass for backward compatibility (see
+ * https://github.com/google/gson/pull/1787#issuecomment-1222175189) even
+ * though normally TypeAdapterRuntimeTypeWrapper should prefer the custom
+ * serializer for Base.
+ */
+ @Test
+ public void testJsonDeserializer_SubclassBackwardCompatibility() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Subclass.class, new JsonDeserializer() {
+ @Override
+ public Subclass deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ throw new AssertionError("not needed for this test");
+ }
+ })
+ .registerTypeAdapter(Base.class, new JsonSerializer () {
+ @Override
+ public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive("base");
+ }
+ })
+ .create();
+
+ String json = gson.toJson(new Container());
+ assertEquals("{\"b\":{\"f\":\"test\"}}", json);
+ }
+
+ private static class CyclicBase {
+ @SuppressWarnings("unused")
+ CyclicBase f;
+ }
+
+ private static class CyclicSub extends CyclicBase {
+ @SuppressWarnings("unused")
+ int i;
+
+ public CyclicSub(int i) {
+ this.i = i;
+ }
+ }
+
+ /**
+ * Tests behavior when the type of a field refers to a type whose adapter is
+ * currently in the process of being created. For these cases {@link Gson}
+ * uses a future adapter for the type. That adapter later uses the actual
+ * adapter as delegate.
+ */
+ @Test
+ public void testGsonFutureAdapter() {
+ CyclicBase b = new CyclicBase();
+ b.f = new CyclicSub(2);
+ String json = new Gson().toJson(b);
+ assertEquals("{\"f\":{\"i\":2}}", json);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/VersioningTest.java b/gson/src/test/java/com/google/gson/functional/VersioningTest.java
index 2416fc06..49dabcab 100644
--- a/gson/src/test/java/com/google/gson/functional/VersioningTest.java
+++ b/gson/src/test/java/com/google/gson/functional/VersioningTest.java
@@ -15,13 +15,17 @@
*/
package com.google.gson.functional;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until;
import com.google.gson.common.TestTypes.BagOfPrimitives;
-
-import junit.framework.TestCase;
+import org.junit.Test;
/**
* Functional tests for versioning support in Gson.
@@ -29,47 +33,60 @@ import junit.framework.TestCase;
* @author Inderjeet Singh
* @author Joel Leitch
*/
-public class VersioningTest extends TestCase {
+public class VersioningTest {
private static final int A = 0;
private static final int B = 1;
private static final int C = 2;
private static final int D = 3;
- private GsonBuilder builder;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- builder = new GsonBuilder();
+ private static Gson gsonWithVersion(double version) {
+ return new GsonBuilder().setVersion(version).create();
}
+ @Test
public void testVersionedUntilSerialization() {
Version1 target = new Version1();
- Gson gson = builder.setVersion(1.29).create();
+ Gson gson = gsonWithVersion(1.29);
String json = gson.toJson(target);
assertTrue(json.contains("\"a\":" + A));
- gson = builder.setVersion(1.3).create();
+ gson = gsonWithVersion(1.3);
+ json = gson.toJson(target);
+ assertFalse(json.contains("\"a\":" + A));
+
+ gson = gsonWithVersion(1.31);
json = gson.toJson(target);
assertFalse(json.contains("\"a\":" + A));
}
+ @Test
public void testVersionedUntilDeserialization() {
- Gson gson = builder.setVersion(1.3).create();
String json = "{\"a\":3,\"b\":4,\"c\":5}";
+
+ Gson gson = gsonWithVersion(1.29);
Version1 version1 = gson.fromJson(json, Version1.class);
+ assertEquals(3, version1.a);
+
+ gson = gsonWithVersion(1.3);
+ version1 = gson.fromJson(json, Version1.class);
+ assertEquals(A, version1.a);
+
+ gson = gsonWithVersion(1.31);
+ version1 = gson.fromJson(json, Version1.class);
assertEquals(A, version1.a);
}
+ @Test
public void testVersionedClassesSerialization() {
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
String json1 = gson.toJson(new Version1());
String json2 = gson.toJson(new Version1_1());
assertEquals(json1, json2);
}
+ @Test
public void testVersionedClassesDeserialization() {
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
String json = "{\"a\":3,\"b\":4,\"c\":5}";
Version1 version1 = gson.fromJson(json, Version1.class);
assertEquals(3, version1.a);
@@ -80,13 +97,15 @@ public class VersioningTest extends TestCase {
assertEquals(C, version1_1.c);
}
+ @Test
public void testIgnoreLaterVersionClassSerialization() {
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
assertEquals("null", gson.toJson(new Version1_2()));
}
+ @Test
public void testIgnoreLaterVersionClassDeserialization() {
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
String json = "{\"a\":3,\"b\":4,\"c\":5,\"d\":6}";
Version1_2 version1_2 = gson.fromJson(json, Version1_2.class);
// Since the class is versioned to be after 1.0, we expect null
@@ -94,14 +113,16 @@ public class VersioningTest extends TestCase {
assertNull(version1_2);
}
+ @Test
public void testVersionedGsonWithUnversionedClassesSerialization() {
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
BagOfPrimitives target = new BagOfPrimitives(10, 20, false, "stringValue");
assertEquals(target.getExpectedJson(), gson.toJson(target));
}
+ @Test
public void testVersionedGsonWithUnversionedClassesDeserialization() {
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
String json = "{\"longValue\":10,\"intValue\":20,\"booleanValue\":false}";
BagOfPrimitives expected = new BagOfPrimitives();
@@ -112,34 +133,45 @@ public class VersioningTest extends TestCase {
assertEquals(expected, actual);
}
+ @Test
public void testVersionedGsonMixingSinceAndUntilSerialization() {
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
SinceUntilMixing target = new SinceUntilMixing();
String json = gson.toJson(target);
assertFalse(json.contains("\"b\":" + B));
- gson = builder.setVersion(1.2).create();
+ gson = gsonWithVersion(1.2);
json = gson.toJson(target);
assertTrue(json.contains("\"b\":" + B));
- gson = builder.setVersion(1.3).create();
+ gson = gsonWithVersion(1.3);
+ json = gson.toJson(target);
+ assertFalse(json.contains("\"b\":" + B));
+
+ gson = gsonWithVersion(1.4);
json = gson.toJson(target);
assertFalse(json.contains("\"b\":" + B));
}
+ @Test
public void testVersionedGsonMixingSinceAndUntilDeserialization() {
String json = "{\"a\":5,\"b\":6}";
- Gson gson = builder.setVersion(1.0).create();
+ Gson gson = gsonWithVersion(1.0);
SinceUntilMixing result = gson.fromJson(json, SinceUntilMixing.class);
assertEquals(5, result.a);
assertEquals(B, result.b);
- gson = builder.setVersion(1.2).create();
+ gson = gsonWithVersion(1.2);
result = gson.fromJson(json, SinceUntilMixing.class);
assertEquals(5, result.a);
assertEquals(6, result.b);
- gson = builder.setVersion(1.3).create();
+ gson = gsonWithVersion(1.3);
+ result = gson.fromJson(json, SinceUntilMixing.class);
+ assertEquals(5, result.a);
+ assertEquals(B, result.b);
+
+ gson = gsonWithVersion(1.4);
result = gson.fromJson(json, SinceUntilMixing.class);
assertEquals(5, result.a);
assertEquals(B, result.b);
diff --git a/gson/src/test/java/com/google/gson/internal/UnsafeAllocatorInstantiationTest.java b/gson/src/test/java/com/google/gson/internal/UnsafeAllocatorInstantiationTest.java
index e3ce147e..54d0a506 100644
--- a/gson/src/test/java/com/google/gson/internal/UnsafeAllocatorInstantiationTest.java
+++ b/gson/src/test/java/com/google/gson/internal/UnsafeAllocatorInstantiationTest.java
@@ -37,9 +37,8 @@ public final class UnsafeAllocatorInstantiationTest extends TestCase {
* to instantiate an interface
*/
public void testInterfaceInstantiation() throws Exception {
- UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
try {
- unsafeAllocator.newInstance(Interface.class);
+ UnsafeAllocator.INSTANCE.newInstance(Interface.class);
fail();
} catch (AssertionError e) {
assertTrue(e.getMessage().startsWith("UnsafeAllocator is used for non-instantiable type"));
@@ -51,9 +50,8 @@ public final class UnsafeAllocatorInstantiationTest extends TestCase {
* to instantiate an abstract class
*/
public void testAbstractClassInstantiation() throws Exception {
- UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
try {
- unsafeAllocator.newInstance(AbstractClass.class);
+ UnsafeAllocator.INSTANCE.newInstance(AbstractClass.class);
fail();
} catch (AssertionError e) {
assertTrue(e.getMessage().startsWith("UnsafeAllocator is used for non-instantiable type"));
@@ -64,8 +62,7 @@ public final class UnsafeAllocatorInstantiationTest extends TestCase {
* Ensure that no exception is thrown when trying to instantiate a concrete class
*/
public void testConcreteClassInstantiation() throws Exception {
- UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
- ConcreteClass instance = unsafeAllocator.newInstance(ConcreteClass.class);
+ ConcreteClass instance = UnsafeAllocator.INSTANCE.newInstance(ConcreteClass.class);
assertNotNull(instance);
}
}
diff --git a/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java
index 5a63f3cb..c20a3683 100644
--- a/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java
+++ b/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java
@@ -22,14 +22,12 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
-
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.JavaVersion;
import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType;
import com.google.gson.reflect.TypeToken;
-
import junit.framework.TestCase;
/**
@@ -75,6 +73,39 @@ public class DefaultDateTypeAdapterTest extends TestCase {
}
}
+ public void testParsingDatesFormattedWithSystemLocale() throws Exception {
+ // TODO(eamonnmcmanus): fix this test, which fails on JDK 8 and 17
+ if (JavaVersion.getMajorJavaVersion() != 11) {
+ return;
+ }
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.FRANCE);
+ try {
+ String afterYearSep = JavaVersion.isJava9OrLater() ? " à " : " ";
+ assertParsed(String.format("1 janv. 1970%s00:00:00", afterYearSep),
+ DateType.DATE.createDefaultsAdapterFactory());
+ assertParsed("01/01/70", DateType.DATE.createAdapterFactory(DateFormat.SHORT));
+ assertParsed("1 janv. 1970", DateType.DATE.createAdapterFactory(DateFormat.MEDIUM));
+ assertParsed("1 janvier 1970", DateType.DATE.createAdapterFactory(DateFormat.LONG));
+ assertParsed("01/01/70 00:00",
+ DateType.DATE.createAdapterFactory(DateFormat.SHORT, DateFormat.SHORT));
+ assertParsed(String.format("1 janv. 1970%s00:00:00", afterYearSep),
+ DateType.DATE.createAdapterFactory(DateFormat.MEDIUM, DateFormat.MEDIUM));
+ assertParsed(String.format("1 janvier 1970%s00:00:00 UTC", afterYearSep),
+ DateType.DATE.createAdapterFactory(DateFormat.LONG, DateFormat.LONG));
+ assertParsed(JavaVersion.isJava9OrLater() ? (JavaVersion.getMajorJavaVersion() <11 ?
+ "jeudi 1 janvier 1970 à 00:00:00 Coordinated Universal Time" :
+ "jeudi 1 janvier 1970 à 00:00:00 Temps universel coordonné") :
+ "jeudi 1 janvier 1970 00 h 00 UTC",
+ DateType.DATE.createAdapterFactory(DateFormat.FULL, DateFormat.FULL));
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
public void testParsingDatesFormattedWithUsLocale() throws Exception {
TimeZone defaultTimeZone = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
diff --git a/gson/src/test/java/com/google/gson/internal/bind/Java17ReflectiveTypeAdapterFactoryTest.java b/gson/src/test/java/com/google/gson/internal/bind/Java17ReflectiveTypeAdapterFactoryTest.java
new file mode 100644
index 00000000..18984c7b
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/internal/bind/Java17ReflectiveTypeAdapterFactoryTest.java
@@ -0,0 +1,81 @@
+package com.google.gson.internal.bind;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.internal.reflect.Java17ReflectionHelperTest;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.UserPrincipal;
+import java.security.Principal;
+import org.junit.Before;
+import org.junit.Test;
+
+public class Java17ReflectiveTypeAdapterFactoryTest {
+
+ // The class jdk.net.UnixDomainPrincipal is one of the few Record types that are included in the JDK.
+ // We use this to test serialization and deserialization of Record classes, so we do not need to
+ // have record support at the language level for these tests. This class was added in JDK 16.
+ Class> unixDomainPrincipalClass;
+
+ @Before
+ public void setUp() throws Exception {
+ unixDomainPrincipalClass = Class.forName("jdk.net.UnixDomainPrincipal");
+ }
+
+ // Class for which the normal reflection based adapter is used
+ private static class DummyClass {
+ @SuppressWarnings("unused")
+ public String s;
+ }
+
+ @Test
+ public void testCustomAdapterForRecords() {
+ Gson gson = new Gson();
+ TypeAdapter> recordAdapter = gson.getAdapter(unixDomainPrincipalClass);
+ TypeAdapter> defaultReflectionAdapter = gson.getAdapter(DummyClass.class);
+ assertNotEquals(recordAdapter.getClass(), defaultReflectionAdapter.getClass());
+ }
+
+ @Test
+ public void testSerializeRecords() throws ReflectiveOperationException {
+ Gson gson =
+ new GsonBuilder()
+ .registerTypeAdapter(UserPrincipal.class, new PrincipalTypeAdapter<>())
+ .registerTypeAdapter(GroupPrincipal.class, new PrincipalTypeAdapter<>())
+ .create();
+
+ UserPrincipal userPrincipal = gson.fromJson("\"user\"", UserPrincipal.class);
+ GroupPrincipal groupPrincipal = gson.fromJson("\"group\"", GroupPrincipal.class);
+ Object recordInstance =
+ unixDomainPrincipalClass
+ .getDeclaredConstructor(UserPrincipal.class, GroupPrincipal.class)
+ .newInstance(userPrincipal, groupPrincipal);
+ String serialized = gson.toJson(recordInstance);
+ Object deserializedRecordInstance = gson.fromJson(serialized, unixDomainPrincipalClass);
+
+ assertEquals(recordInstance, deserializedRecordInstance);
+ assertEquals("{\"user\":\"user\",\"group\":\"group\"}", serialized);
+ }
+
+ private static class PrincipalTypeAdapter extends TypeAdapter {
+ @Override
+ public void write(JsonWriter out, T principal) throws IOException {
+ out.value(principal.getName());
+ }
+
+ @Override
+ public T read(JsonReader in) throws IOException {
+ final String name = in.nextString();
+ // This type adapter is only used for Group and User Principal, both of which are implemented by PrincipalImpl.
+ @SuppressWarnings("unchecked")
+ T principal = (T) new Java17ReflectionHelperTest.PrincipalImpl(name);
+ return principal;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java
index 4e6a218e..2ac32a0e 100644
--- a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java
+++ b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java
@@ -29,12 +29,15 @@ import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
+import static org.junit.Assert.assertThrows;
+
@SuppressWarnings("resource")
public class JsonTreeReaderTest extends TestCase {
public void testSkipValue_emptyJsonObject() throws IOException {
JsonTreeReader in = new JsonTreeReader(new JsonObject());
in.skipValue();
assertEquals(JsonToken.END_DOCUMENT, in.peek());
+ assertEquals("$", in.getPath());
}
public void testSkipValue_filledJsonObject() throws IOException {
@@ -53,6 +56,46 @@ public class JsonTreeReaderTest extends TestCase {
JsonTreeReader in = new JsonTreeReader(jsonObject);
in.skipValue();
assertEquals(JsonToken.END_DOCUMENT, in.peek());
+ assertEquals("$", in.getPath());
+ }
+
+ public void testSkipValue_name() throws IOException {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("a", "value");
+ JsonTreeReader in = new JsonTreeReader(jsonObject);
+ in.beginObject();
+ in.skipValue();
+ assertEquals(JsonToken.STRING, in.peek());
+ assertEquals("$.", in.getPath());
+ assertEquals("value", in.nextString());
+ }
+
+ public void testSkipValue_afterEndOfDocument() throws IOException {
+ JsonTreeReader reader = new JsonTreeReader(new JsonObject());
+ reader.beginObject();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+
+ assertEquals("$", reader.getPath());
+ assertThrows("Attempt to skip led outside the document", IllegalStateException.class, reader::skipValue);
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ assertEquals("$", reader.getPath());
+ }
+
+ public void testSkipValue_atArrayEnd() throws IOException {
+ JsonTreeReader reader = new JsonTreeReader(new JsonArray());
+ reader.beginArray();
+ assertThrows("Attempt to skip led outside its parent", IllegalStateException.class, reader::skipValue);
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ assertEquals("$", reader.getPath());
+ }
+
+ public void testSkipValue_atObjectEnd() throws IOException {
+ JsonTreeReader reader = new JsonTreeReader(new JsonObject());
+ reader.beginObject();
+ assertThrows("Attempt to skip led outside its parent", IllegalStateException.class, reader::skipValue);
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ assertEquals("$", reader.getPath());
}
public void testHasNext_endOfDocument() throws IOException {
@@ -92,7 +135,8 @@ public class JsonTreeReaderTest extends TestCase {
* {@code JsonReader} must be overridden.
*/
public void testOverrides() {
- List ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()");
+ List ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()",
+ "setSerializeSpecialFloatingPointValues(boolean)", "isSerializeSpecialFloatingPointValues()");
MoreAsserts.assertOverridesMethods(JsonReader.class, JsonTreeReader.class, ignoredMethods);
}
}
diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
index 126390b6..19e30f34 100644
--- a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
+++ b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
@@ -256,8 +256,12 @@ public final class JsonTreeWriterTest extends TestCase {
* methods of {@code JsonWriter} must be overridden.
*/
public void testOverrides() {
- List