summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Ostrovsky <david.ostrovsky@gmail.com>2022-05-23 05:56:36 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2022-05-23 05:56:36 +0000
commit0841ec669073549ccb91fa866b79bac5b70d2779 (patch)
tree91494d13604d76a5c4d5425c4ab1f618c58a2d29
parent2bbc81266dac79d3d8550b5ac4307b263aa8a366 (diff)
parentb23cefde61cfcb08128405d266282ccf28be3757 (diff)
Merge changes I64b553f2,Ied9fc2f7 into stable-3.6
* changes: Introduce general purpose Optional JSON serializer for GSON Add Optional type JSON (de)serialization unit tests
-rw-r--r--java/com/google/gerrit/json/OptionalTypeAdapter.java69
-rw-r--r--java/com/google/gerrit/server/notedb/ChangeNoteJson.java2
-rw-r--r--javatests/com/google/gerrit/server/notedb/ChangeNoteJsonTest.java127
3 files changed, 198 insertions, 0 deletions
diff --git a/java/com/google/gerrit/json/OptionalTypeAdapter.java b/java/com/google/gerrit/json/OptionalTypeAdapter.java
new file mode 100644
index 0000000000..9bfa72dfde
--- /dev/null
+++ b/java/com/google/gerrit/json/OptionalTypeAdapter.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// 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.gerrit.json;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.inject.TypeLiteral;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Optional;
+
+public class OptionalTypeAdapter
+ implements JsonSerializer<Optional<?>>, JsonDeserializer<Optional<?>> {
+
+ private static final String VALUE = "value";
+
+ @Override
+ public JsonElement serialize(Optional<?> src, Type typeOfSrc, JsonSerializationContext context) {
+ Optional<?> optional = src == null ? Optional.empty() : src;
+ JsonObject json = new JsonObject();
+ json.add(VALUE, optional.map(context::serialize).orElse(JsonNull.INSTANCE));
+ return json;
+ }
+
+ @Override
+ public Optional<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!json.getAsJsonObject().has(VALUE)) {
+ return Optional.empty();
+ }
+
+ JsonElement value = json.getAsJsonObject().get(VALUE);
+ if (value == null || value.isJsonNull()) {
+ return Optional.empty();
+ }
+
+ // handle the situation when one uses Optional without type parameter which is an equivalent of
+ // <?> type
+ ParameterizedType parameterizedType =
+ (ParameterizedType) new TypeLiteral<Optional<?>>() {}.getType();
+ if (typeOfT instanceof ParameterizedType) {
+ parameterizedType = (ParameterizedType) typeOfT;
+ if (parameterizedType.getActualTypeArguments().length != 1) {
+ throw new JsonParseException("Expected one parameter type in Optional.");
+ }
+ }
+
+ Type optionalOf = parameterizedType.getActualTypeArguments()[0];
+ return Optional.of(context.deserialize(value, optionalOf));
+ }
+}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
index 37a38fe403..0f1d362c76 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.EntitiesAdapterFactory;
import com.google.gerrit.json.EnumTypeAdapterFactory;
import com.google.gerrit.json.OptionalSubmitRequirementExpressionResultAdapterFactory;
+import com.google.gerrit.json.OptionalTypeAdapter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
@@ -52,6 +53,7 @@ public class ChangeNoteJson {
static Gson newGson() {
return new GsonBuilder()
+ .registerTypeAdapter(Optional.class, new OptionalTypeAdapter())
.registerTypeAdapter(Timestamp.class, new CommentTimestampAdapter().nullSafe())
.registerTypeAdapterFactory(new EnumTypeAdapterFactory())
.registerTypeAdapterFactory(EntitiesAdapterFactory.create())
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNoteJsonTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNoteJsonTest.java
new file mode 100644
index 0000000000..24e28f376f
--- /dev/null
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNoteJsonTest.java
@@ -0,0 +1,127 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// 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.gerrit.server.notedb;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+
+import com.google.gson.Gson;
+import com.google.inject.TypeLiteral;
+import java.util.Optional;
+import org.junit.Test;
+
+public class ChangeNoteJsonTest {
+ private final Gson gson = new ChangeNoteJson().getGson();
+
+ static class Child {
+ Optional<String> optionalValue;
+ }
+
+ static class Parent {
+ Optional<Child> optionalChild;
+ }
+
+ @Test
+ public void shouldSerializeAndDeserializeEmptyOptional() {
+ // given
+ Optional<?> empty = Optional.empty();
+
+ // when
+ String json = gson.toJson(empty);
+
+ // then
+ assertThat(json).isEqualTo("{}");
+
+ // and when
+ Optional<?> result = gson.fromJson(json, Optional.class);
+
+ // and then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void shouldSerializeAndDeserializeNonEmptyOptional() {
+ // given
+ String value = "foo";
+ Optional<String> nonEmpty = Optional.of(value);
+
+ // when
+ String json = gson.toJson(nonEmpty);
+
+ // then
+ assertThat(json).isEqualTo("{\n \"value\": \"" + value + "\"\n}");
+
+ // and when
+ Optional<String> result = gson.fromJson(json, new TypeLiteral<Optional<String>>() {}.getType());
+
+ // and then
+ assertThat(result).isPresent();
+ assertThat(result.get()).isEqualTo(value);
+ }
+
+ @Test
+ public void shouldSerializeAndDeserializeNestedNonEmptyOptional() {
+ String value = "foo";
+ Child fooChild = new Child();
+ fooChild.optionalValue = Optional.of(value);
+ Parent parent = new Parent();
+ parent.optionalChild = Optional.of(fooChild);
+
+ String json = gson.toJson(parent);
+
+ assertThat(json)
+ .isEqualTo(
+ "{\n"
+ + " \"optionalChild\": {\n"
+ + " \"value\": {\n"
+ + " \"optionalValue\": {\n"
+ + " \"value\": \"foo\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "}");
+
+ Parent result = gson.fromJson(json, new TypeLiteral<Parent>() {}.getType());
+
+ assertThat(result.optionalChild).isPresent();
+ assertThat(result.optionalChild.get().optionalValue).isPresent();
+ assertThat(result.optionalChild.get().optionalValue.get()).isEqualTo(value);
+ }
+
+ @Test
+ public void shouldSerializeAndDeserializeNestedEmptyOptional() {
+ Child fooChild = new Child();
+ fooChild.optionalValue = Optional.empty();
+ Parent parent = new Parent();
+ parent.optionalChild = Optional.of(fooChild);
+
+ String json = gson.toJson(parent);
+
+ assertThat(json)
+ .isEqualTo(
+ "{\n"
+ + " \"optionalChild\": {\n"
+ + " \"value\": {\n"
+ + " \"optionalValue\": {}\n"
+ + " }\n"
+ + " }\n"
+ + "}");
+
+ Parent result = gson.fromJson(json, new TypeLiteral<Parent>() {}.getType());
+
+ assertThat(result.optionalChild).isPresent();
+ assertThat(result.optionalChild.get().optionalValue).isEmpty();
+ }
+}