aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcono1234 <Marcono1234@users.noreply.github.com>2022-10-03 01:38:43 +0200
committerGitHub <noreply@github.com>2022-10-02 16:38:43 -0700
commit796193d0326a2f44bc314bf24262732ea3e64014 (patch)
treeb51271871198fd1615228898ef0874276d256b67
parent28609089faa747f2ad5730281c14093ab40d6fda (diff)
downloadgson-796193d0326a2f44bc314bf24262732ea3e64014.tar.gz
Improve versioning support documentation and validate version (#2214)
-rw-r--r--gson/src/main/java/com/google/gson/GsonBuilder.java23
-rw-r--r--gson/src/main/java/com/google/gson/annotations/Since.java12
-rw-r--r--gson/src/main/java/com/google/gson/annotations/Until.java18
-rw-r--r--gson/src/main/java/com/google/gson/internal/Excluder.java8
-rw-r--r--gson/src/test/java/com/google/gson/GsonBuilderTest.java33
-rw-r--r--gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java70
-rw-r--r--gson/src/test/java/com/google/gson/functional/VersioningTest.java80
7 files changed, 180 insertions, 64 deletions
diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java
index 38c7e909..50757b3b 100644
--- a/gson/src/main/java/com/google/gson/GsonBuilder.java
+++ b/gson/src/main/java/com/google/gson/GsonBuilder.java
@@ -28,6 +28,8 @@ import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
+import com.google.gson.annotations.Since;
+import com.google.gson.annotations.Until;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.DefaultDateTypeAdapter;
@@ -143,14 +145,25 @@ public final class GsonBuilder {
}
/**
- * Configures Gson to enable versioning support.
+ * Configures Gson to enable versioning support. Versioning support works based on the
+ * annotation types {@link Since} and {@link Until}. It allows including or excluding fields
+ * and classes based on the specified version. See the documentation of these annotation
+ * types for more information.
*
- * @param ignoreVersionsAfter any field or type marked with a version higher than this value
- * are ignored during serialization or deserialization.
+ * <p>By default versioning support is disabled and usage of {@code @Since} and {@code @Until}
+ * has no effect.
+ *
+ * @param version the version number to use.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @throws IllegalArgumentException if the version number is NaN or negative
+ * @see Since
+ * @see Until
*/
- public GsonBuilder setVersion(double ignoreVersionsAfter) {
- excluder = excluder.withVersion(ignoreVersionsAfter);
+ public GsonBuilder setVersion(double version) {
+ if (Double.isNaN(version) || version < 0.0) {
+ throw new IllegalArgumentException("Invalid version: " + version);
+ }
+ excluder = excluder.withVersion(version);
return this;
}
diff --git a/gson/src/main/java/com/google/gson/annotations/Since.java b/gson/src/main/java/com/google/gson/annotations/Since.java
index e23b6ec9..a7e51fc1 100644
--- a/gson/src/main/java/com/google/gson/annotations/Since.java
+++ b/gson/src/main/java/com/google/gson/annotations/Since.java
@@ -16,6 +16,7 @@
package com.google.gson.annotations;
+import com.google.gson.GsonBuilder;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -24,12 +25,11 @@ import java.lang.annotation.Target;
/**
* An annotation that indicates the version number since a member or a type has been present.
- * This annotation is useful to manage versioning of your Json classes for a web-service.
+ * This annotation is useful to manage versioning of your JSON classes for a web-service.
*
* <p>
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
- * {@link com.google.gson.GsonBuilder} and invoke
- * {@link com.google.gson.GsonBuilder#setVersion(double)} method.
+ * {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
*
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre>
@@ -50,14 +50,16 @@ import java.lang.annotation.Target;
*
* @author Inderjeet Singh
* @author Joel Leitch
+ * @see GsonBuilder#setVersion(double)
+ * @see Until
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since {
/**
- * the value indicating a version number since this member
- * or type has been present.
+ * The value indicating a version number since this member or type has been present.
+ * The number is inclusive; annotated elements will be included if {@code gsonVersion >= value}.
*/
double value();
}
diff --git a/gson/src/main/java/com/google/gson/annotations/Until.java b/gson/src/main/java/com/google/gson/annotations/Until.java
index 7c61d104..a5fcabd4 100644
--- a/gson/src/main/java/com/google/gson/annotations/Until.java
+++ b/gson/src/main/java/com/google/gson/annotations/Until.java
@@ -16,6 +16,7 @@
package com.google.gson.annotations;
+import com.google.gson.GsonBuilder;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -24,14 +25,13 @@ import java.lang.annotation.Target;
/**
* An annotation that indicates the version number until a member or a type should be present.
- * Basically, if Gson is created with a version number that exceeds the value stored in the
- * {@code Until} annotation then the field will be ignored from the JSON output. This annotation
- * is useful to manage versioning of your JSON classes for a web-service.
+ * Basically, if Gson is created with a version number that is equal to or exceeds the value
+ * stored in the {@code Until} annotation then the field will be ignored from the JSON output.
+ * This annotation is useful to manage versioning of your JSON classes for a web-service.
*
* <p>
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
- * {@link com.google.gson.GsonBuilder} and invoke
- * {@link com.google.gson.GsonBuilder#setVersion(double)} method.
+ * {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
*
* <p>Here is an example of how this annotation is meant to be used:</p>
* <pre>
@@ -47,12 +47,14 @@ import java.lang.annotation.Target;
* methods will use all the fields for serialization and deserialization. However, if you created
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress}
- * and {@code password} fields from the example above, because the version number passed to the
+ * and {@code password} fields from the example above, because the version number passed to the
* GsonBuilder, {@code 1.2}, exceeds the version number set on the {@code Until} annotation,
* {@code 1.1}, for those fields.
*
* @author Inderjeet Singh
* @author Joel Leitch
+ * @see GsonBuilder#setVersion(double)
+ * @see Since
* @since 1.3
*/
@Documented
@@ -61,8 +63,8 @@ import java.lang.annotation.Target;
public @interface Until {
/**
- * the value indicating a version number until this member
- * or type should be ignored.
+ * The value indicating a version number until this member or type should be be included.
+ * The number is exclusive; annotated elements will be included if {@code gsonVersion < value}.
*/
double value();
}
diff --git a/gson/src/main/java/com/google/gson/internal/Excluder.java b/gson/src/main/java/com/google/gson/internal/Excluder.java
index 8d8a25f4..03bd45cb 100644
--- a/gson/src/main/java/com/google/gson/internal/Excluder.java
+++ b/gson/src/main/java/com/google/gson/internal/Excluder.java
@@ -240,9 +240,7 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
private boolean isValidSince(Since annotation) {
if (annotation != null) {
double annotationVersion = annotation.value();
- if (annotationVersion > version) {
- return false;
- }
+ return version >= annotationVersion;
}
return true;
}
@@ -250,9 +248,7 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
private boolean isValidUntil(Until annotation) {
if (annotation != null) {
double annotationVersion = annotation.value();
- if (annotationVersion <= version) {
- return false;
- }
+ return version < annotationVersion;
}
return true;
}
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<Object> NULL_TYPE_ADAPTER = new TypeAdapter<Object>() {
@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/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));
+ }
+
+ @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));
}
- 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));
+ @Since(VERSION)
+ 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)
- private static class MockObject {
+ @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/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);