From 94c9ea2fe854a70640941bbeb7df7f10ee4a14f5 Mon Sep 17 00:00:00 2001 From: Mike Friesen Date: Thu, 14 Nov 2024 21:09:37 -0600 Subject: [PATCH 1/7] Give FieldNamingStrategy the ability to return multiple String names --- .../com/google/gson/FieldNamingStrategy.java | 14 ++++++++++++- .../bind/ReflectiveTypeAdapterFactory.java | 6 ++++-- .../gson/functional/NamingPolicyTest.java | 20 +++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java index 541588696e..b4c96c29c9 100644 --- a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java +++ b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java @@ -17,6 +17,8 @@ package com.google.gson; import java.lang.reflect.Field; +import java.util.Collections; +import java.util.List; /** * A mechanism for providing custom field naming in Gson. This allows the client code to translate @@ -33,8 +35,18 @@ public interface FieldNamingStrategy { * Translates the field name into its JSON field name representation. * * @param f the field object that we are translating - * @return the translated field name. + * @return the list of possible translated field names. * @since 1.3 */ public String translateName(Field f); + + /** + * Translates the field name into its JSON field alternative names representation. + * + * @param f the field object that we are translating + * @return the list of possible translated field names. + */ + default List translateToAlternateNames(Field f) { + return Collections.emptyList(); + } } diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java index 94396ff1eb..056583a6d7 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -86,8 +86,10 @@ private boolean includeField(Field f, boolean serialize) { private List getFieldNames(Field f) { SerializedName annotation = f.getAnnotation(SerializedName.class); if (annotation == null) { - String name = fieldNamingPolicy.translateName(f); - return Collections.singletonList(name); + String fieldName = fieldNamingPolicy.translateName(f); + List fieldNames = new ArrayList<>(fieldNamingPolicy.translateToAlternateNames(f)); + fieldNames.add(fieldName); + return fieldNames; } String serializedName = annotation.value(); diff --git a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java index c419f6155d..a9a705befe 100644 --- a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java +++ b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java @@ -26,6 +26,7 @@ import com.google.gson.common.TestTypes.ClassWithSerializedNameFields; import com.google.gson.common.TestTypes.StringWrapper; import java.lang.reflect.Field; +import java.util.List; import java.util.Locale; import org.junit.Before; import org.junit.Test; @@ -237,6 +238,25 @@ public void testAtSignInSerializedName() { assertThat(new Gson().toJson(new AtName())).isEqualTo("{\"@foo\":\"bar\"}"); } + @Test + public void testGsonWithAlternateNamesDeserialiation() { + Gson gson = builder.setFieldNamingStrategy(new FieldNamingStrategy() { + + @Override + public String translateName(Field f) { + return "badname"; + } + + @Override + public List translateToAlternateNames(Field f) { + return List.of("SomeConstantStringInstanceField"); + } + }).create(); + String target = "{\"SomeConstantStringInstanceField\":\"someValue\"}"; + StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class); + assertThat(deserializedObject.someConstantStringInstanceField).isEqualTo("someValue"); + } + static final class AtName { @SerializedName("@foo") String f = "bar"; From 4ebc2c744b7391f654339fcbd12833ffacbf6ba5 Mon Sep 17 00:00:00 2001 From: Mike Friesen Date: Sun, 17 Nov 2024 09:43:36 -0600 Subject: [PATCH 2/7] Fixed formatting violations --- .../gson/functional/NamingPolicyTest.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java index a9a705befe..d41ed6a831 100644 --- a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java +++ b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java @@ -240,18 +240,22 @@ public void testAtSignInSerializedName() { @Test public void testGsonWithAlternateNamesDeserialiation() { - Gson gson = builder.setFieldNamingStrategy(new FieldNamingStrategy() { - - @Override - public String translateName(Field f) { - return "badname"; - } - - @Override - public List translateToAlternateNames(Field f) { - return List.of("SomeConstantStringInstanceField"); - } - }).create(); + Gson gson = + builder + .setFieldNamingStrategy( + new FieldNamingStrategy() { + + @Override + public String translateName(Field f) { + return "badname"; + } + + @Override + public List translateToAlternateNames(Field f) { + return List.of("SomeConstantStringInstanceField"); + } + }) + .create(); String target = "{\"SomeConstantStringInstanceField\":\"someValue\"}"; StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class); assertThat(deserializedObject.someConstantStringInstanceField).isEqualTo("someValue"); From 05a58e6985e991e792fba3b471bd28facf6beb23 Mon Sep 17 00:00:00 2001 From: Mike Friesen Date: Sun, 17 Nov 2024 11:58:03 -0600 Subject: [PATCH 3/7] Code review changes - Changed fieldName to be added first to the fieldName list - test which verifies that when 'alternate names' are configured they don't affect serialization, and only affect deserialization - Updated translateToAlternateNames JavaDoc to refer it works like SerializedName#alternate() --- .../com/google/gson/FieldNamingStrategy.java | 6 +++-- .../bind/ReflectiveTypeAdapterFactory.java | 8 +++--- .../gson/functional/NamingPolicyTest.java | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java index b4c96c29c9..6cc8fc5787 100644 --- a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java +++ b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java @@ -16,6 +16,7 @@ package com.google.gson; +import com.google.gson.annotations.SerializedName; import java.lang.reflect.Field; import java.util.Collections; import java.util.List; @@ -35,13 +36,14 @@ public interface FieldNamingStrategy { * Translates the field name into its JSON field name representation. * * @param f the field object that we are translating - * @return the list of possible translated field names. + * @return the translated field name. * @since 1.3 */ public String translateName(Field f); /** - * Translates the field name into its JSON field alternative names representation. + * Translates the field name into its JSON field alternative names representation. used for + * deserialization only. This is similar to {@link SerializedName#alternate()}. * * @param f the field object that we are translating * @return the list of possible translated field names. diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java index 056583a6d7..d9337b9d9e 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -55,6 +55,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** Type adapter that reflects over the fields and methods of a class. */ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { @@ -87,9 +89,9 @@ private List getFieldNames(Field f) { SerializedName annotation = f.getAnnotation(SerializedName.class); if (annotation == null) { String fieldName = fieldNamingPolicy.translateName(f); - List fieldNames = new ArrayList<>(fieldNamingPolicy.translateToAlternateNames(f)); - fieldNames.add(fieldName); - return fieldNames; + List alternateNames = fieldNamingPolicy.translateToAlternateNames(f); + return Stream.concat(Stream.of(fieldName), alternateNames.stream()) + .collect(Collectors.toList()); } String serializedName = annotation.value(); diff --git a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java index d41ed6a831..a1ddde6318 100644 --- a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java +++ b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java @@ -261,6 +261,32 @@ public List translateToAlternateNames(Field f) { assertThat(deserializedObject.someConstantStringInstanceField).isEqualTo("someValue"); } + @Test + public void testGsonWithAlternateNamesSerialization() { + Gson gson = + builder + .setFieldNamingStrategy( + new FieldNamingStrategy() { + + @Override + public String translateName(Field f) { + return "some-constant-string-instance-field"; + } + + @Override + public List translateToAlternateNames(Field f) { + return List.of("SomeConstantStringInstanceField"); + } + }) + .create(); + StringWrapper target = new StringWrapper("blah"); + assertThat(gson.toJson(target)) + .isEqualTo( + "{\"some-constant-string-instance-field\":\"" + + target.someConstantStringInstanceField + + "\"}"); + } + static final class AtName { @SerializedName("@foo") String f = "bar"; From c9e39dd0edde0a80f8431c1eda0afc47c93436ce Mon Sep 17 00:00:00 2001 From: Mike Friesen Date: Sun, 17 Nov 2024 17:06:29 -0600 Subject: [PATCH 4/7] Removed usage of Stream.concat Check Android compatibility test fails when using Stream.concat, switched to traditional Java method. --- .../internal/bind/ReflectiveTypeAdapterFactory.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java index d9337b9d9e..689b811371 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -55,8 +55,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** Type adapter that reflects over the fields and methods of a class. */ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { @@ -89,9 +87,11 @@ private List getFieldNames(Field f) { SerializedName annotation = f.getAnnotation(SerializedName.class); if (annotation == null) { String fieldName = fieldNamingPolicy.translateName(f); - List alternateNames = fieldNamingPolicy.translateToAlternateNames(f); - return Stream.concat(Stream.of(fieldName), alternateNames.stream()) - .collect(Collectors.toList()); + List alternates = fieldNamingPolicy.translateToAlternateNames(f); + List fieldNames = new ArrayList<>(alternates.size() + 1); + fieldNames.add(fieldName); + fieldNames.addAll(alternates); + return fieldNames; } String serializedName = annotation.value(); From 705e97bab972f5d960249d0385b80697bd0e6e4d Mon Sep 17 00:00:00 2001 From: Mike Friesen Date: Wed, 20 Nov 2024 20:51:48 -0600 Subject: [PATCH 5/7] Code Review Changes 1. Renamed badname to primary-name 2. Added test deserializing TranslateName with translateToAlternateNames --- .../gson/functional/NamingPolicyTest.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java index a1ddde6318..f3a14697b9 100644 --- a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java +++ b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java @@ -238,6 +238,29 @@ public void testAtSignInSerializedName() { assertThat(new Gson().toJson(new AtName())).isEqualTo("{\"@foo\":\"bar\"}"); } + @Test + public void testGsonWithNameDeserialiation() { + Gson gson = + builder + .setFieldNamingStrategy( + new FieldNamingStrategy() { + + @Override + public String translateName(Field f) { + return "SomeConstantStringInstanceField"; + } + + @Override + public List translateToAlternateNames(Field f) { + return List.of("alternate-name"); + } + }) + .create(); + String target = "{\"SomeConstantStringInstanceField\":\"someValue\"}"; + StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class); + assertThat(deserializedObject.someConstantStringInstanceField).isEqualTo("someValue"); + } + @Test public void testGsonWithAlternateNamesDeserialiation() { Gson gson = @@ -247,7 +270,7 @@ public void testGsonWithAlternateNamesDeserialiation() { @Override public String translateName(Field f) { - return "badname"; + return "primary-name"; } @Override From 5bab616ebd50b3555001eccc9c57459391803e32 Mon Sep 17 00:00:00 2001 From: Mike Friesen Date: Wed, 20 Nov 2024 20:52:39 -0600 Subject: [PATCH 6/7] Fixed typo --- gson/src/main/java/com/google/gson/FieldNamingStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java index 6cc8fc5787..a6cb607d2d 100644 --- a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java +++ b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java @@ -42,7 +42,7 @@ public interface FieldNamingStrategy { public String translateName(Field f); /** - * Translates the field name into its JSON field alternative names representation. used for + * Translates the field name into its JSON field alternative names representation. Used for * deserialization only. This is similar to {@link SerializedName#alternate()}. * * @param f the field object that we are translating From d88b41507285623a453a37de4228efac7b313c7b Mon Sep 17 00:00:00 2001 From: Mike Friesen Date: Wed, 20 Nov 2024 20:52:56 -0600 Subject: [PATCH 7/7] added @since $next-version$ to translateToAlternateNames method --- gson/src/main/java/com/google/gson/FieldNamingStrategy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java index a6cb607d2d..9e145a81c4 100644 --- a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java +++ b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java @@ -47,6 +47,7 @@ public interface FieldNamingStrategy { * * @param f the field object that we are translating * @return the list of possible translated field names. + * @since $next-version$ */ default List translateToAlternateNames(Field f) { return Collections.emptyList();