diff options
author | Marcono1234 <Marcono1234@users.noreply.github.com> | 2022-10-03 01:38:43 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-02 16:38:43 -0700 |
commit | 796193d0326a2f44bc314bf24262732ea3e64014 (patch) | |
tree | b51271871198fd1615228898ef0874276d256b67 | |
parent | 28609089faa747f2ad5730281c14093ab40d6fda (diff) | |
download | gson-796193d0326a2f44bc314bf24262732ea3e64014.tar.gz |
Improve versioning support documentation and validate version (#2214)
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); |