Give FieldNamingStrategy the ability to return multiple String names (#2776)

* Give FieldNamingStrategy the ability to return multiple String names

* Fixed formatting violations

* 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()

* Removed usage of Stream.concat

Check Android compatibility test fails when using Stream.concat, switched to traditional Java method.

* Code Review Changes

1. Renamed badname to primary-name
2. Added test deserializing TranslateName with translateToAlternateNames

* Fixed typo

* added @since $next-version$ to translateToAlternateNames method

* Renamed translateToAlternateNames to alternateNames

* Merged the code with and without the SerializedName annotation

* Improved FieldNamingStrategy JavaDoc for the alternateNames method.

* Added special handling for annotation without alternate names

* Moved Collections.singletonList block to apply to both cases

* Update gson/src/main/java/com/google/gson/FieldNamingStrategy.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

---------

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
This commit is contained in:
Mike Friesen
2025-04-13 19:04:16 -04:00
committed by GitHub
parent 6010131366
commit 4e65e6ab36
3 changed files with 101 additions and 9 deletions

View File

@@ -16,7 +16,10 @@
package com.google.gson;
import com.google.gson.annotations.SerializedName;
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
@@ -37,4 +40,16 @@ public interface FieldNamingStrategy {
* @since 1.3
*/
public String translateName(Field f);
/**
* Returns alternative names for this field when it is being deserialized. This is similar to
* {@link SerializedName#alternate()}.
*
* @param f the field object
* @return the list of alternative field names.
* @since $next-version$
*/
default List<String> alternateNames(Field f) {
return Collections.emptyList();
}
}

View File

@@ -84,21 +84,25 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
/** first element holds the default name */
@SuppressWarnings("MixedMutabilityReturnType")
private List<String> getFieldNames(Field f) {
String fieldName;
List<String> alternates;
SerializedName annotation = f.getAnnotation(SerializedName.class);
if (annotation == null) {
String name = fieldNamingPolicy.translateName(f);
return Collections.singletonList(name);
fieldName = fieldNamingPolicy.translateName(f);
alternates = fieldNamingPolicy.alternateNames(f);
} else {
fieldName = annotation.value();
alternates = Arrays.asList(annotation.alternate());
}
String serializedName = annotation.value();
String[] alternates = annotation.alternate();
if (alternates.length == 0) {
return Collections.singletonList(serializedName);
if (alternates.isEmpty()) {
return Collections.singletonList(fieldName);
}
List<String> fieldNames = new ArrayList<>(alternates.length + 1);
fieldNames.add(serializedName);
Collections.addAll(fieldNames, alternates);
List<String> fieldNames = new ArrayList<>(alternates.size() + 1);
fieldNames.add(fieldName);
fieldNames.addAll(alternates);
return fieldNames;
}

View File

@@ -26,6 +26,7 @@ import com.google.gson.annotations.SerializedName;
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,78 @@ public class NamingPolicyTest {
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 "primary-name";
}
@Override
public List<String> alternateNames(Field f) {
return List.of("alternate-name");
}
})
.create();
String target = "{\"primary-name\":\"someValue\"}";
StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class);
assertThat(deserializedObject.someConstantStringInstanceField).isEqualTo("someValue");
}
@Test
public void testGsonWithAlternateNamesDeserialiation() {
Gson gson =
builder
.setFieldNamingStrategy(
new FieldNamingStrategy() {
@Override
public String translateName(Field f) {
return "primary-name";
}
@Override
public List<String> alternateNames(Field f) {
return List.of("alternate-name");
}
})
.create();
String target = "{\"alternate-name\":\"someValue\"}";
StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class);
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<String> alternateNames(Field f) {
return List.of("alternate-name");
}
})
.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";