diff options
author | David Ostrovsky <david.ostrovsky@gmail.com> | 2022-05-23 05:56:36 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2022-05-23 05:56:36 +0000 |
commit | 0841ec669073549ccb91fa866b79bac5b70d2779 (patch) | |
tree | 91494d13604d76a5c4d5425c4ab1f618c58a2d29 | |
parent | 2bbc81266dac79d3d8550b5ac4307b263aa8a366 (diff) | |
parent | b23cefde61cfcb08128405d266282ccf28be3757 (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
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(); + } +} |