From cae0d97f16a96ce904fa4935dfc53ca712e36f5c Mon Sep 17 00:00:00 2001
From: Andrew Szeto <andrew@jabagawee.com>
Date: Mon, 10 Jun 2024 23:33:53 -0700
Subject: [PATCH 1/7] Change the parameters for the
 `getCustSerializedName(FieldDescriptor)` method

In the other commits in this PR, I plan to introduce branching logic inside of
the customization of the serialized name for fields. This change is a pure
refactor that serves to isolate the business logic into a separate commit so as
to make it easier to understand.
---
 .../com/google/gson/protobuf/ProtoTypeAdapter.java | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
index 7b872f1885..d44d6db761 100644
--- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
+++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
@@ -224,7 +224,7 @@ public JsonElement serialize(Message src, Type typeOfSrc, JsonSerializationConte
 
     for (Map.Entry<FieldDescriptor, Object> fieldPair : fields.entrySet()) {
       final FieldDescriptor desc = fieldPair.getKey();
-      String name = getCustSerializedName(desc.getOptions(), desc.getName());
+      String name = getCustSerializedName(desc);
 
       if (desc.getType() == ENUM_TYPE) {
         // Enum collections are also returned as ENUM_TYPE
@@ -272,8 +272,7 @@ public Message deserialize(JsonElement json, Type typeOfT, JsonDeserializationCo
           (Descriptor) getCachedMethod(protoClass, "getDescriptor").invoke(null);
       // Call setters on all of the available fields
       for (FieldDescriptor fieldDescriptor : protoDescriptor.getFields()) {
-        String jsonFieldName =
-            getCustSerializedName(fieldDescriptor.getOptions(), fieldDescriptor.getName());
+        String jsonFieldName = getCustSerializedName(fieldDescriptor);
 
         JsonElement jsonElement = jsonObject.get(jsonFieldName);
         if (jsonElement != null && !jsonElement.isJsonNull()) {
@@ -317,16 +316,17 @@ public Message deserialize(JsonElement json, Type typeOfT, JsonDeserializationCo
   }
 
   /**
-   * Retrieves the custom field name from the given options, and if not found, returns the specified
-   * default name.
+   * Retrieves the custom field name for a given FieldDescriptor via its field options, falling back
+   * to its name as a default.
    */
-  private String getCustSerializedName(FieldOptions options, String defaultName) {
+  private String getCustSerializedName(FieldDescriptor fieldDescriptor) {
+    FieldOptions options = fieldDescriptor.getOptions();
     for (Extension<FieldOptions, String> extension : serializedNameExtensions) {
       if (options.hasExtension(extension)) {
         return options.getExtension(extension);
       }
     }
-    return protoFormat.to(jsonFormat, defaultName);
+    return protoFormat.to(jsonFormat, fieldDescriptor.getName());
   }
 
   /**

From 4907723bc246c50137685ebb2ce26bebd38d0ce0 Mon Sep 17 00:00:00 2001
From: Andrew Szeto <andrew@jabagawee.com>
Date: Mon, 10 Jun 2024 23:52:27 -0700
Subject: [PATCH 2/7] Allow reading `json_name` field option for proto
 serialization

---
 .../gson/protobuf/ProtoTypeAdapter.java       | 36 +++++++++++++++++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
index d44d6db761..5bc8110e13 100644
--- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
+++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
@@ -89,6 +89,7 @@ public static class Builder {
     private EnumSerialization enumSerialization;
     private CaseFormat protoFormat;
     private CaseFormat jsonFormat;
+    private boolean shouldUseJsonNameFieldOption;
 
     private Builder(
         EnumSerialization enumSerialization,
@@ -98,6 +99,7 @@ private Builder(
       this.serializedEnumValueExtensions = new HashSet<>();
       setEnumSerialization(enumSerialization);
       setFieldNameSerializationFormat(fromFieldNameFormat, toFieldNameFormat);
+      this.shouldUseJsonNameFieldOption = false;
     }
 
     @CanIgnoreReturnValue
@@ -174,13 +176,37 @@ public Builder addSerializedEnumValueExtension(
       return this;
     }
 
+    /**
+     * Sets or unsets a flag that, when set, causes the adapter to use the `json_name` field option
+     * from a proto field for serialization. This `json_name` option is an annotation applied to
+     * proto fields that cannot be read from the descriptor's options
+     * (`FieldDescriptor::getOptions`) as it is only available via `FieldDescriptor::getJsonName`.
+     *
+     * <p>This flag is subordinate to any custom serialized name extensions added to this adapter.
+     * In other words, serialized name extensions take precedence over this setting. For example, a
+     * field defined like:
+     *
+     * <pre>
+     * string client_app_id = 1 [json_name = "foo", (serialized_name) = "bar"];
+     * </pre>
+     *
+     * ...will be serialized as '{@code bar}' if `shouldUseJsonNameFieldOption` is set to `true` and
+     * the '{@code serialized_name}' annotation is added to the adapter.
+     */
+    @CanIgnoreReturnValue
+    public Builder setShouldUseJsonNameFieldOption(boolean shouldUseJsonNameFieldOption) {
+      this.shouldUseJsonNameFieldOption = shouldUseJsonNameFieldOption;
+      return this;
+    }
+
     public ProtoTypeAdapter build() {
       return new ProtoTypeAdapter(
           enumSerialization,
           protoFormat,
           jsonFormat,
           serializedNameExtensions,
-          serializedEnumValueExtensions);
+          serializedEnumValueExtensions,
+          shouldUseJsonNameFieldOption);
     }
   }
 
@@ -203,18 +229,21 @@ public static Builder newBuilder() {
   private final CaseFormat jsonFormat;
   private final Set<Extension<FieldOptions, String>> serializedNameExtensions;
   private final Set<Extension<EnumValueOptions, String>> serializedEnumValueExtensions;
+  private final boolean shouldUseJsonNameFieldOption;
 
   private ProtoTypeAdapter(
       EnumSerialization enumSerialization,
       CaseFormat protoFormat,
       CaseFormat jsonFormat,
       Set<Extension<FieldOptions, String>> serializedNameExtensions,
-      Set<Extension<EnumValueOptions, String>> serializedEnumValueExtensions) {
+      Set<Extension<EnumValueOptions, String>> serializedEnumValueExtensions,
+      boolean shouldUseJsonNameFieldOption) {
     this.enumSerialization = enumSerialization;
     this.protoFormat = protoFormat;
     this.jsonFormat = jsonFormat;
     this.serializedNameExtensions = serializedNameExtensions;
     this.serializedEnumValueExtensions = serializedEnumValueExtensions;
+    this.shouldUseJsonNameFieldOption = shouldUseJsonNameFieldOption;
   }
 
   @Override
@@ -326,6 +355,9 @@ private String getCustSerializedName(FieldDescriptor fieldDescriptor) {
         return options.getExtension(extension);
       }
     }
+    if (fieldDescriptor.toProto().hasJsonName() && shouldUseJsonNameFieldOption) {
+      return fieldDescriptor.getJsonName();
+    }
     return protoFormat.to(jsonFormat, fieldDescriptor.getName());
   }
 

From 5c555a6d41b66ec9f1f4791fd1306dc57f4ed38c Mon Sep 17 00:00:00 2001
From: Andrew Szeto <andrew@jabagawee.com>
Date: Mon, 10 Jun 2024 23:59:26 -0700
Subject: [PATCH 3/7] Add tests for reading `json_name` field option

---
 ...ProtosWithAnnotationsAndJsonNamesTest.java | 206 ++++++++++++++++++
 proto/src/test/proto/bag.proto                |   9 +-
 2 files changed, 214 insertions(+), 1 deletion(-)
 create mode 100644 proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java

diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
new file mode 100644
index 0000000000..6b181fa41d
--- /dev/null
+++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.gson.protobuf.functional;
+
+import static com.google.common.truth.Truth.assertThat;
+// import static org.junit.Assert.assertThrows;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+// import com.google.gson.JsonParseException;
+import com.google.gson.protobuf.ProtoTypeAdapter;
+import com.google.gson.protobuf.generated.Annotations;
+import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotationsAndJsonNames;
+import com.google.protobuf.GeneratedMessage;
+import java.util.Map;
+import org.junit.Test;
+
+/**
+ * Functional tests for protocol buffers using annotations and custom json_name values for field
+ * names.
+ *
+ * @author Andrew Szeto
+ */
+public class ProtosWithAnnotationsAndJsonNamesTest {
+  private static final Gson GSON_PLAIN =
+      new GsonBuilder()
+          .registerTypeHierarchyAdapter(
+              GeneratedMessage.class, ProtoTypeAdapter.newBuilder().build())
+          .create();
+  private static final Gson GSON_WITH_SERIALIZED_NAME =
+      new GsonBuilder()
+          .registerTypeHierarchyAdapter(
+              GeneratedMessage.class,
+              ProtoTypeAdapter.newBuilder()
+                  .addSerializedNameExtension(Annotations.serializedName)
+                  .setShouldUseJsonNameFieldOption(false)
+                  .build())
+          .create();
+  private static final Gson GSON_WITH_JSON_NAME =
+      new GsonBuilder()
+          .registerTypeHierarchyAdapter(
+              GeneratedMessage.class,
+              ProtoTypeAdapter.newBuilder().setShouldUseJsonNameFieldOption(true).build())
+          .create();
+  private static final Gson GSON_WITH_SERIALIZED_NAME_AND_JSON_NAME =
+      new GsonBuilder()
+          .registerTypeHierarchyAdapter(
+              GeneratedMessage.class,
+              ProtoTypeAdapter.newBuilder()
+                  .addSerializedNameExtension(Annotations.serializedName)
+                  .setShouldUseJsonNameFieldOption(true)
+                  .build())
+          .create();
+
+  private static final Map<Gson, String> JSON_OUTPUTS =
+      Map.of(
+          GSON_PLAIN,
+              "{\"neither\":\"xxx\",\"jsonNameOnly\":\"yyy\",\"annotationOnly\":\"zzz\",\"both\":\"www\"}",
+          GSON_WITH_JSON_NAME,
+              "{\"neither\":\"xxx\",\"aaa\":\"yyy\",\"annotationOnly\":\"zzz\",\"ccc\":\"www\"}",
+          GSON_WITH_SERIALIZED_NAME,
+              "{\"neither\":\"xxx\",\"jsonNameOnly\":\"yyy\",\"bbb\":\"zzz\",\"ddd\":\"www\"}",
+          GSON_WITH_SERIALIZED_NAME_AND_JSON_NAME,
+              "{\"neither\":\"xxx\",\"aaa\":\"yyy\",\"bbb\":\"zzz\",\"ddd\":\"www\"}");
+
+  private static final ProtoWithAnnotationsAndJsonNames PROTO =
+      ProtoWithAnnotationsAndJsonNames.newBuilder()
+          .setNeither("xxx")
+          .setJsonNameOnly("yyy")
+          .setAnnotationOnly("zzz")
+          .setBoth("www")
+          .build();
+
+  @Test
+  public void testProtoWithAnnotationsAndJsonNames_basicConversions() {
+    JSON_OUTPUTS.forEach(
+        (gson, json) -> {
+          assertThat(gson.fromJson(json, ProtoWithAnnotationsAndJsonNames.class)).isEqualTo(PROTO);
+          assertThat(gson.toJson(PROTO)).isEqualTo(json);
+        });
+  }
+
+  @Test
+  public void testProtoWithAnnotationsAndJsonNames_basicRoundTrips() {
+    JSON_OUTPUTS.forEach(
+        (gson, json) -> {
+          assertThat(roundTrip(gson, gson, json)).isEqualTo(json);
+          assertThat(roundTrip(gson, gson, PROTO)).isEqualTo(PROTO);
+        });
+  }
+
+  @Test
+  public void testProtoWithAnnotationsAndJsonNames_unannotatedField() {
+    ProtoWithAnnotationsAndJsonNames proto =
+        ProtoWithAnnotationsAndJsonNames.newBuilder().setNeither("zzz").build();
+    String json = "{\"neither\":\"zzz\"}";
+
+    for (Gson gson1 : JSON_OUTPUTS.keySet()) {
+      for (Gson gson2 : JSON_OUTPUTS.keySet()) {
+        // all configs should match with each other in how they serialize this proto, and they
+        // should be able to deserialize any other config's serialization of the proto back to its
+        // original form
+        assertThat(gson1.toJson(proto)).isEqualTo(gson2.toJson(proto));
+        assertThat(roundTrip(gson1, gson2, proto)).isEqualTo(proto);
+        assertThat(roundTrip(gson2, gson1, proto)).isEqualTo(proto);
+        // the same, but in the other direction
+        assertThat(gson1.fromJson(json, ProtoWithAnnotationsAndJsonNames.class))
+            .isEqualTo(gson2.fromJson(json, ProtoWithAnnotationsAndJsonNames.class));
+        assertThat(roundTrip(gson1, gson2, json)).isEqualTo(json);
+        assertThat(roundTrip(gson2, gson1, json)).isEqualTo(json);
+      }
+    }
+  }
+
+  @Test
+  public void testProtoWithAnnotationsAndJsonNames_fieldWithJsonName() {
+    ProtoWithAnnotationsAndJsonNames proto =
+        ProtoWithAnnotationsAndJsonNames.newBuilder().setJsonNameOnly("zzz").build();
+    String jsonWithoutJsonName = "{\"jsonNameOnly\":\"zzz\"}";
+    String jsonWithJsonName = "{\"aaa\":\"zzz\"}";
+
+    // the ProtoTypeAdapter that checks for the custom annotation should default to the basic name
+    assertThat(GSON_PLAIN.toJson(proto)).isEqualTo(jsonWithoutJsonName);
+    assertThat(GSON_WITH_SERIALIZED_NAME.toJson(proto)).isEqualTo(GSON_PLAIN.toJson(proto));
+
+    // the ProtoTypeAdapter that respects the `json_name` option should not have the same output as
+    // the base case
+    assertThat(GSON_WITH_JSON_NAME.toJson(proto)).isNotEqualTo(GSON_PLAIN.toJson(proto));
+
+    // both ProtoTypeAdapters that set shouldUseJsonNameFieldOption to true should match in output
+    assertThat(GSON_WITH_JSON_NAME.toJson(proto)).isEqualTo(jsonWithJsonName);
+    assertThat(GSON_WITH_JSON_NAME.toJson(proto))
+        .isEqualTo(GSON_WITH_SERIALIZED_NAME_AND_JSON_NAME.toJson(proto));
+
+    // should fail to round-trip if we serialize via the `json_name` and deserialize without it or
+    // vice versa
+    assertThat(roundTrip(GSON_PLAIN, GSON_WITH_JSON_NAME, proto)).isNotEqualTo(proto);
+    assertThat(roundTrip(GSON_WITH_JSON_NAME, GSON_PLAIN, proto)).isNotEqualTo(proto);
+  }
+
+  @Test
+  public void testProtoWithAnnotationsAndJsonNames_fieldWithCustomSerializedName() {
+    ProtoWithAnnotationsAndJsonNames proto =
+        ProtoWithAnnotationsAndJsonNames.newBuilder().setAnnotationOnly("zzz").build();
+    String jsonWithoutCustomName = "{\"annotationOnly\":\"zzz\"}";
+    String jsonWithCustomName = "{\"bbb\":\"zzz\"}";
+
+    // the ProtoTypeAdapter that checks for the json name should default to the basic name
+    assertThat(GSON_PLAIN.toJson(proto)).isEqualTo(jsonWithoutCustomName);
+    assertThat(GSON_WITH_JSON_NAME.toJson(proto)).isEqualTo(GSON_PLAIN.toJson(proto));
+
+    // the ProtoTypeAdapter that checks for the custom serialized name should not have the same
+    // output as the base case
+    assertThat(GSON_WITH_SERIALIZED_NAME.toJson(proto)).isNotEqualTo(GSON_PLAIN.toJson(proto));
+
+    // both ProtoTypeAdapters that check for the custom serialized name should match in output
+    assertThat(GSON_WITH_SERIALIZED_NAME.toJson(proto)).isEqualTo(jsonWithCustomName);
+    assertThat(GSON_WITH_SERIALIZED_NAME.toJson(proto))
+        .isEqualTo(GSON_WITH_SERIALIZED_NAME_AND_JSON_NAME.toJson(proto));
+
+    // should fail to round-trip if we serialize via the custom name and deserialize without it or
+    // vice versa
+    assertThat(roundTrip(GSON_PLAIN, GSON_WITH_SERIALIZED_NAME, proto)).isNotEqualTo(proto);
+    assertThat(roundTrip(GSON_WITH_SERIALIZED_NAME, GSON_PLAIN, proto)).isNotEqualTo(proto);
+  }
+
+  @Test
+  public void testProtoWithAnnotationsAndJsonNames_fieldWithJsonNameAndCustomSerializedName() {
+    ProtoWithAnnotationsAndJsonNames proto =
+        ProtoWithAnnotationsAndJsonNames.newBuilder().setBoth("zzz").build();
+    String jsonPlain = "{\"both\":\"zzz\"}";
+    String jsonWithJsonName = "{\"ccc\":\"zzz\"}";
+    String jsonWithCustomName = "{\"ddd\":\"zzz\"}";
+
+    // the three different configs serialize to three different values
+    assertThat(GSON_PLAIN.toJson(proto)).isEqualTo(jsonPlain);
+    assertThat(GSON_WITH_JSON_NAME.toJson(proto)).isEqualTo(jsonWithJsonName);
+    assertThat(GSON_WITH_SERIALIZED_NAME.toJson(proto)).isEqualTo(jsonWithCustomName);
+
+    // the case where both configs are enabled will prefer the custom annotation
+    assertThat(GSON_WITH_SERIALIZED_NAME_AND_JSON_NAME.toJson(proto))
+        .isEqualTo(GSON_WITH_SERIALIZED_NAME.toJson(proto));
+  }
+
+  private static String roundTrip(Gson jsonToProto, Gson protoToJson, String json) {
+    return protoToJson.toJson(jsonToProto.fromJson(json, ProtoWithAnnotationsAndJsonNames.class));
+  }
+
+  private static ProtoWithAnnotationsAndJsonNames roundTrip(
+      Gson protoToJson, Gson jsonToProto, ProtoWithAnnotationsAndJsonNames proto) {
+    return jsonToProto.fromJson(protoToJson.toJson(proto), ProtoWithAnnotationsAndJsonNames.class);
+  }
+}
diff --git a/proto/src/test/proto/bag.proto b/proto/src/test/proto/bag.proto
index 3e4769e2a8..df3eee1890 100644
--- a/proto/src/test/proto/bag.proto
+++ b/proto/src/test/proto/bag.proto
@@ -69,4 +69,11 @@ message ProtoWithAnnotations {
   }
   optional InnerMessage inner_message_1 = 3;
   optional InnerMessage inner_message_2 = 4;
-}
\ No newline at end of file
+}
+
+message ProtoWithAnnotationsAndJsonNames {
+  optional string neither = 1;
+  optional string json_name_only = 2 [json_name = "aaa"];
+  optional string annotation_only = 3 [(serialized_name) = "bbb"];
+  optional string both = 4 [json_name = "ccc", (serialized_name) = "ddd"];
+}

From 6ccba7da92a8ed929284a31fb30b5fda4d0243ad Mon Sep 17 00:00:00 2001
From: Andrew Szeto <andrew@jabagawee.com>
Date: Thu, 13 Jun 2024 20:42:02 -0700
Subject: [PATCH 4/7] Add some metadata to Javadoc according to contributing
 guidelines

---
 .../main/java/com/google/gson/protobuf/ProtoTypeAdapter.java   | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
index 5bc8110e13..393e7c24d2 100644
--- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
+++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
@@ -192,6 +192,9 @@ public Builder addSerializedEnumValueExtension(
      *
      * ...will be serialized as '{@code bar}' if `shouldUseJsonNameFieldOption` is set to `true` and
      * the '{@code serialized_name}' annotation is added to the adapter.
+     *
+     * @author Andrew Szeto
+     * @since $next-version$
      */
     @CanIgnoreReturnValue
     public Builder setShouldUseJsonNameFieldOption(boolean shouldUseJsonNameFieldOption) {

From 5515f36a62f954260c0883fe2c7fc67a9698b468 Mon Sep 17 00:00:00 2001
From: Andrew Szeto <andrew@jabagawee.com>
Date: Thu, 13 Jun 2024 20:47:46 -0700
Subject: [PATCH 5/7] Remove @author annotation in Javadoc

---
 .../src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
index 393e7c24d2..f03750913d 100644
--- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
+++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
@@ -193,7 +193,6 @@ public Builder addSerializedEnumValueExtension(
      * ...will be serialized as '{@code bar}' if `shouldUseJsonNameFieldOption` is set to `true` and
      * the '{@code serialized_name}' annotation is added to the adapter.
      *
-     * @author Andrew Szeto
      * @since $next-version$
      */
     @CanIgnoreReturnValue

From 07918d6e1891113c90df3cfb72df895096a01280 Mon Sep 17 00:00:00 2001
From: Andrew Szeto <andrew@jabagawee.com>
Date: Thu, 20 Jun 2024 03:00:45 -0700
Subject: [PATCH 6/7] Update branch based on PR feedback on GitHub

---
 .../google/gson/protobuf/ProtoTypeAdapter.java    | 15 ++++++++-------
 .../ProtosWithAnnotationsAndJsonNamesTest.java    |  4 ----
 2 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
index f03750913d..30146c54fc 100644
--- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
+++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java
@@ -177,10 +177,11 @@ public Builder addSerializedEnumValueExtension(
     }
 
     /**
-     * Sets or unsets a flag that, when set, causes the adapter to use the `json_name` field option
-     * from a proto field for serialization. This `json_name` option is an annotation applied to
-     * proto fields that cannot be read from the descriptor's options
-     * (`FieldDescriptor::getOptions`) as it is only available via `FieldDescriptor::getJsonName`.
+     * Sets or unsets a flag (default false) that, when set, causes the adapter to use the {@code
+     * json_name} field option from a proto field for serialization. Unlike other field options that
+     * can be defined as annotations on a proto field, {@code json_name} cannot be accessed via a
+     * proto field's {@link FieldDescriptor#getOptions} and registered via {@link
+     * ProtoTypeAdapter.Builder#addSerializedNameExtension}.
      *
      * <p>This flag is subordinate to any custom serialized name extensions added to this adapter.
      * In other words, serialized name extensions take precedence over this setting. For example, a
@@ -190,8 +191,8 @@ public Builder addSerializedEnumValueExtension(
      * string client_app_id = 1 [json_name = "foo", (serialized_name) = "bar"];
      * </pre>
      *
-     * ...will be serialized as '{@code bar}' if `shouldUseJsonNameFieldOption` is set to `true` and
-     * the '{@code serialized_name}' annotation is added to the adapter.
+     * ...will be serialized as '{@code bar}' if {@code shouldUseJsonNameFieldOption} is set to
+     * {@code true} and the '{@code serialized_name}' annotation is added to the adapter.
      *
      * @since $next-version$
      */
@@ -357,7 +358,7 @@ private String getCustSerializedName(FieldDescriptor fieldDescriptor) {
         return options.getExtension(extension);
       }
     }
-    if (fieldDescriptor.toProto().hasJsonName() && shouldUseJsonNameFieldOption) {
+    if (shouldUseJsonNameFieldOption && fieldDescriptor.toProto().hasJsonName()) {
       return fieldDescriptor.getJsonName();
     }
     return protoFormat.to(jsonFormat, fieldDescriptor.getName());
diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
index 6b181fa41d..abcb0c5451 100644
--- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
+++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
@@ -16,11 +16,9 @@
 package com.google.gson.protobuf.functional;
 
 import static com.google.common.truth.Truth.assertThat;
-// import static org.junit.Assert.assertThrows;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-// import com.google.gson.JsonParseException;
 import com.google.gson.protobuf.ProtoTypeAdapter;
 import com.google.gson.protobuf.generated.Annotations;
 import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotationsAndJsonNames;
@@ -115,12 +113,10 @@ public void testProtoWithAnnotationsAndJsonNames_unannotatedField() {
         // original form
         assertThat(gson1.toJson(proto)).isEqualTo(gson2.toJson(proto));
         assertThat(roundTrip(gson1, gson2, proto)).isEqualTo(proto);
-        assertThat(roundTrip(gson2, gson1, proto)).isEqualTo(proto);
         // the same, but in the other direction
         assertThat(gson1.fromJson(json, ProtoWithAnnotationsAndJsonNames.class))
             .isEqualTo(gson2.fromJson(json, ProtoWithAnnotationsAndJsonNames.class));
         assertThat(roundTrip(gson1, gson2, json)).isEqualTo(json);
-        assertThat(roundTrip(gson2, gson1, json)).isEqualTo(json);
       }
     }
   }

From c7f37dd8813d10b488133fae3ad572d580d984a4 Mon Sep 17 00:00:00 2001
From: Andrew Szeto <andrew@jabagawee.com>
Date: Fri, 21 Jun 2024 18:01:24 -0700
Subject: [PATCH 7/7] Update copyright year on test file

---
 .../functional/ProtosWithAnnotationsAndJsonNamesTest.java       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
index abcb0c5451..7eeae44f41 100644
--- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
+++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsAndJsonNamesTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2024 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.