From 6eac3c6c1b992305fec82a6143e141e5131847c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Mon, 11 Dec 2023 10:49:49 +0100 Subject: [PATCH 1/8] another approach to fix 2563 --- gson/src/main/java/com/google/gson/Gson.java | 4 ++- .../gson/internal/bind/ObjectTypeAdapter.java | 25 +++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 80aa12888e..1bbfd3189e 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -324,7 +324,7 @@ public Gson() { // built-in type adapters that cannot be overridden factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); - factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy)); + factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy, true)); // the excluder must precede all adapters that handle user-defined types factories.add(excluder); @@ -332,6 +332,8 @@ public Gson() { // users' type adapters factories.addAll(factoriesToBeAdded); + factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy, false)); + // type adapters for basic platform types factories.add(TypeAdapters.STRING_FACTORY); factories.add(TypeAdapters.INTEGER_FACTORY); diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java index 20d1606291..2d279e93a4 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -27,6 +27,8 @@ import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import java.io.IOException; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -39,7 +41,7 @@ */ public final class ObjectTypeAdapter extends TypeAdapter { /** Gson default factory using {@link ToNumberPolicy#DOUBLE}. */ - private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE); + private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE, true); private final Gson gson; private final ToNumberStrategy toNumberStrategy; @@ -49,24 +51,37 @@ private ObjectTypeAdapter(Gson gson, ToNumberStrategy toNumberStrategy) { this.toNumberStrategy = toNumberStrategy; } - private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { + private static TypeAdapterFactory newFactory( + final ToNumberStrategy toNumberStrategy, final boolean skipTypeVariable) { return new TypeAdapterFactory() { @SuppressWarnings("unchecked") @Override public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getRawType() == Object.class) { + if (type.getRawType() == Object.class + && (!skipTypeVariable || !isTypeVariableWithBound(type.getType()))) { return (TypeAdapter) new ObjectTypeAdapter(gson, toNumberStrategy); } return null; } + + private boolean isTypeVariableWithBound(Type type) { + if (type instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) type; + Type bound = tv.getBounds()[0]; + return bound != Object.class && bound instanceof Class; + } else { + return false; + } + } }; } - public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { + public static TypeAdapterFactory getFactory( + ToNumberStrategy toNumberStrategy, boolean skipTypeVariable) { if (toNumberStrategy == ToNumberPolicy.DOUBLE) { return DOUBLE_FACTORY; } else { - return newFactory(toNumberStrategy); + return newFactory(toNumberStrategy, skipTypeVariable); } } From 64896775c07b01353c5e89c8e1de71e02f9fa36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Mon, 11 Dec 2023 11:25:42 +0100 Subject: [PATCH 2/8] backward compatible getFactory --- .../java/com/google/gson/internal/bind/ObjectTypeAdapter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java index 2d279e93a4..541ce8c683 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -76,6 +76,10 @@ private boolean isTypeVariableWithBound(Type type) { }; } + public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { + return getFactory(toNumberStrategy, false); + } + public static TypeAdapterFactory getFactory( ToNumberStrategy toNumberStrategy, boolean skipTypeVariable) { if (toNumberStrategy == ToNumberPolicy.DOUBLE) { From 92f3259b4fd7352c15d7fa3b11a6598fdbb7066c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Mon, 11 Dec 2023 11:31:21 +0100 Subject: [PATCH 3/8] InferenceFromTypeVariableTest --- .../InferenceFromTypeVariableTest.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java diff --git a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java new file mode 100644 index 0000000000..58eb8fdc84 --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java @@ -0,0 +1,77 @@ +package com.google.gson.functional; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import org.junit.Before; +import org.junit.Test; + +/** + * Test deserialization of generic wrapper with type bound. + * + * @author sevcenko + */ +public class InferenceFromTypeVariableTest { + private Gson gson; + + @Before + public void setUp() throws Exception { + gson = new GsonBuilder().registerTypeAdapterFactory(new ResolveGenericBoundFactory()).create(); + } + + public static class Foo { + private final String text; + + public Foo(String text) { + this.text = text; + } + + public String getText() { + return text; + } + } + + public static class BarDynamic { + private final T foo; + + public BarDynamic(T foo) { + this.foo = foo; + } + + public T getFoo() { + return foo; + } + } + + static class ResolveGenericBoundFactory implements TypeAdapterFactory { + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getType() instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) type.getType(); + Type[] bounds = tv.getBounds(); + if (bounds.length == 1 && bounds[0] != Object.class) { + Type bound = bounds[0]; + return (TypeAdapter) gson.getAdapter(TypeToken.get(bound)); + } + } + return null; + } + } + + @Test + public void testSubClassSerialization() { + BarDynamic bar = new BarDynamic<>(new Foo("foo!")); + assertThat(gson.toJson(bar)).isEqualTo("{\"foo\":{\"text\":\"foo!\"}}"); + // without #2563 fix, this would deserialize foo as Object and fails to assign it to foo field + BarDynamic deserialized = gson.fromJson(gson.toJson(bar), BarDynamic.class); + assertThat(deserialized.getFoo().getText()).isEqualTo("foo!"); + } +} From e64900f345fd0fac6e9b9d1da2760d8f123fcec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Mon, 11 Dec 2023 11:46:54 +0100 Subject: [PATCH 4/8] line endings --- .../google/gson/functional/InferenceFromTypeVariableTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java index 58eb8fdc84..657d060024 100644 --- a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java +++ b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java @@ -74,4 +74,4 @@ public void testSubClassSerialization() { BarDynamic deserialized = gson.fromJson(gson.toJson(bar), BarDynamic.class); assertThat(deserialized.getFoo().getText()).isEqualTo("foo!"); } -} +} \ No newline at end of file From 107b99a190de8a430b17080d34b2de62ddfb6ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Mon, 11 Dec 2023 11:49:06 +0100 Subject: [PATCH 5/8] line endings --- .../google/gson/functional/InferenceFromTypeVariableTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java index 657d060024..15e1c58eac 100644 --- a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java +++ b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java @@ -74,4 +74,4 @@ public void testSubClassSerialization() { BarDynamic deserialized = gson.fromJson(gson.toJson(bar), BarDynamic.class); assertThat(deserialized.getFoo().getText()).isEqualTo("foo!"); } -} \ No newline at end of file +} From 9cb957a58cde3a0ab44cf984671e18eba8c92388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Mon, 11 Dec 2023 11:56:51 +0100 Subject: [PATCH 6/8] spotless From afcd75fd57c38a82e308549292bfb4c9737cb373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Mon, 11 Dec 2023 12:02:15 +0100 Subject: [PATCH 7/8] spotless --- .../google/gson/functional/InferenceFromTypeVariableTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java index 15e1c58eac..58eb8fdc84 100644 --- a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java +++ b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java @@ -74,4 +74,4 @@ public void testSubClassSerialization() { BarDynamic deserialized = gson.fromJson(gson.toJson(bar), BarDynamic.class); assertThat(deserialized.getFoo().getText()).isEqualTo("foo!"); } -} +} From 1f5fa7f12b7fe84b5012332941cd5f393cd96572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0ev=C4=8Denko?= Date: Tue, 12 Dec 2023 07:47:31 +0100 Subject: [PATCH 8/8] crlf --- .../InferenceFromTypeVariableTest.java | 154 +++++++++--------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java index 58eb8fdc84..490da5a214 100644 --- a/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java +++ b/gson/src/test/java/com/google/gson/functional/InferenceFromTypeVariableTest.java @@ -1,77 +1,77 @@ -package com.google.gson.functional; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import com.google.gson.TypeAdapterFactory; -import com.google.gson.reflect.TypeToken; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import org.junit.Before; -import org.junit.Test; - -/** - * Test deserialization of generic wrapper with type bound. - * - * @author sevcenko - */ -public class InferenceFromTypeVariableTest { - private Gson gson; - - @Before - public void setUp() throws Exception { - gson = new GsonBuilder().registerTypeAdapterFactory(new ResolveGenericBoundFactory()).create(); - } - - public static class Foo { - private final String text; - - public Foo(String text) { - this.text = text; - } - - public String getText() { - return text; - } - } - - public static class BarDynamic { - private final T foo; - - public BarDynamic(T foo) { - this.foo = foo; - } - - public T getFoo() { - return foo; - } - } - - static class ResolveGenericBoundFactory implements TypeAdapterFactory { - - @SuppressWarnings("unchecked") - @Override - public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getType() instanceof TypeVariable) { - TypeVariable tv = (TypeVariable) type.getType(); - Type[] bounds = tv.getBounds(); - if (bounds.length == 1 && bounds[0] != Object.class) { - Type bound = bounds[0]; - return (TypeAdapter) gson.getAdapter(TypeToken.get(bound)); - } - } - return null; - } - } - - @Test - public void testSubClassSerialization() { - BarDynamic bar = new BarDynamic<>(new Foo("foo!")); - assertThat(gson.toJson(bar)).isEqualTo("{\"foo\":{\"text\":\"foo!\"}}"); - // without #2563 fix, this would deserialize foo as Object and fails to assign it to foo field - BarDynamic deserialized = gson.fromJson(gson.toJson(bar), BarDynamic.class); - assertThat(deserialized.getFoo().getText()).isEqualTo("foo!"); - } -} +package com.google.gson.functional; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import org.junit.Before; +import org.junit.Test; + +/** + * Test deserialization of generic wrapper with type bound. + * + * @author sevcenko + */ +public class InferenceFromTypeVariableTest { + private Gson gson; + + @Before + public void setUp() throws Exception { + gson = new GsonBuilder().registerTypeAdapterFactory(new ResolveGenericBoundFactory()).create(); + } + + public static class Foo { + private final String text; + + public Foo(String text) { + this.text = text; + } + + public String getText() { + return text; + } + } + + public static class BarDynamic { + private final T foo; + + public BarDynamic(T foo) { + this.foo = foo; + } + + public T getFoo() { + return foo; + } + } + + static class ResolveGenericBoundFactory implements TypeAdapterFactory { + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getType() instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) type.getType(); + Type[] bounds = tv.getBounds(); + if (bounds.length == 1 && bounds[0] != Object.class) { + Type bound = bounds[0]; + return (TypeAdapter) gson.getAdapter(TypeToken.get(bound)); + } + } + return null; + } + } + + @Test + public void testSubClassSerialization() { + BarDynamic bar = new BarDynamic<>(new Foo("foo!")); + assertThat(gson.toJson(bar)).isEqualTo("{\"foo\":{\"text\":\"foo!\"}}"); + // without #2563 fix, this would deserialize foo as Object and fails to assign it to foo field + BarDynamic deserialized = gson.fromJson(gson.toJson(bar), BarDynamic.class); + assertThat(deserialized.getFoo().getText()).isEqualTo("foo!"); + } +}