744 lines
25 KiB
Java
744 lines
25 KiB
Java
/*
|
|
* Copyright (C) 2014 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.functional;
|
|
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
|
|
import com.google.gson.ExclusionStrategy;
|
|
import com.google.gson.FieldAttributes;
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
import com.google.gson.JsonDeserializationContext;
|
|
import com.google.gson.JsonDeserializer;
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonPrimitive;
|
|
import com.google.gson.JsonSerializationContext;
|
|
import com.google.gson.JsonSerializer;
|
|
import com.google.gson.TypeAdapter;
|
|
import com.google.gson.TypeAdapterFactory;
|
|
import com.google.gson.annotations.JsonAdapter;
|
|
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
|
|
import com.google.gson.reflect.TypeToken;
|
|
import com.google.gson.stream.JsonReader;
|
|
import com.google.gson.stream.JsonWriter;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.Type;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import org.junit.Test;
|
|
|
|
/** Functional tests for the {@link JsonAdapter} annotation on fields. */
|
|
public final class JsonAdapterAnnotationOnFieldsTest {
|
|
@Test
|
|
public void testClassAnnotationAdapterTakesPrecedenceOverDefault() {
|
|
Gson gson = new Gson();
|
|
String json = gson.toJson(new Computer(new User("Inderjeet Singh")));
|
|
assertThat(json).isEqualTo("{\"user\":\"UserClassAnnotationAdapter\"}");
|
|
Computer computer = gson.fromJson("{'user':'Inderjeet Singh'}", Computer.class);
|
|
assertThat(computer.user.name).isEqualTo("UserClassAnnotationAdapter");
|
|
}
|
|
|
|
@Test
|
|
public void testClassAnnotationAdapterFactoryTakesPrecedenceOverDefault() {
|
|
Gson gson = new Gson();
|
|
String json = gson.toJson(new Gizmo(new Part("Part")));
|
|
assertThat(json).isEqualTo("{\"part\":\"GizmoPartTypeAdapterFactory\"}");
|
|
Gizmo computer = gson.fromJson("{'part':'Part'}", Gizmo.class);
|
|
assertThat(computer.part.name).isEqualTo("GizmoPartTypeAdapterFactory");
|
|
}
|
|
|
|
@Test
|
|
public void testRegisteredTypeAdapterTakesPrecedenceOverClassAnnotationAdapter() {
|
|
Gson gson =
|
|
new GsonBuilder().registerTypeAdapter(User.class, new RegisteredUserAdapter()).create();
|
|
String json = gson.toJson(new Computer(new User("Inderjeet Singh")));
|
|
assertThat(json).isEqualTo("{\"user\":\"RegisteredUserAdapter\"}");
|
|
Computer computer = gson.fromJson("{'user':'Inderjeet Singh'}", Computer.class);
|
|
assertThat(computer.user.name).isEqualTo("RegisteredUserAdapter");
|
|
}
|
|
|
|
@Test
|
|
public void testFieldAnnotationTakesPrecedenceOverRegisteredTypeAdapter() {
|
|
Gson gson =
|
|
new GsonBuilder()
|
|
.registerTypeAdapter(
|
|
Part.class,
|
|
new TypeAdapter<Part>() {
|
|
@Override
|
|
public void write(JsonWriter out, Part part) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Override
|
|
public Part read(JsonReader in) {
|
|
throw new AssertionError();
|
|
}
|
|
})
|
|
.create();
|
|
String json = gson.toJson(new Gadget(new Part("screen")));
|
|
assertThat(json).isEqualTo("{\"part\":\"PartJsonFieldAnnotationAdapter\"}");
|
|
Gadget gadget = gson.fromJson("{'part':'screen'}", Gadget.class);
|
|
assertThat(gadget.part.name).isEqualTo("PartJsonFieldAnnotationAdapter");
|
|
}
|
|
|
|
@Test
|
|
public void testFieldAnnotationTakesPrecedenceOverClassAnnotation() {
|
|
Gson gson = new Gson();
|
|
String json = gson.toJson(new Computer2(new User("Inderjeet Singh")));
|
|
assertThat(json).isEqualTo("{\"user\":\"UserFieldAnnotationAdapter\"}");
|
|
Computer2 target = gson.fromJson("{'user':'Interjeet Singh'}", Computer2.class);
|
|
assertThat(target.user.name).isEqualTo("UserFieldAnnotationAdapter");
|
|
}
|
|
|
|
private static final class Gadget {
|
|
@JsonAdapter(PartJsonFieldAnnotationAdapter.class)
|
|
final Part part;
|
|
|
|
Gadget(Part part) {
|
|
this.part = part;
|
|
}
|
|
}
|
|
|
|
private static final class Gizmo {
|
|
@JsonAdapter(GizmoPartTypeAdapterFactory.class)
|
|
final Part part;
|
|
|
|
Gizmo(Part part) {
|
|
this.part = part;
|
|
}
|
|
}
|
|
|
|
private static final class Part {
|
|
final String name;
|
|
|
|
public Part(String name) {
|
|
this.name = name;
|
|
}
|
|
}
|
|
|
|
private static class PartJsonFieldAnnotationAdapter extends TypeAdapter<Part> {
|
|
@Override
|
|
public void write(JsonWriter out, Part part) throws IOException {
|
|
out.value("PartJsonFieldAnnotationAdapter");
|
|
}
|
|
|
|
@Override
|
|
public Part read(JsonReader in) throws IOException {
|
|
String unused = in.nextString();
|
|
return new Part("PartJsonFieldAnnotationAdapter");
|
|
}
|
|
}
|
|
|
|
private static class GizmoPartTypeAdapterFactory implements TypeAdapterFactory {
|
|
@Override
|
|
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
|
|
return new TypeAdapter<T>() {
|
|
@Override
|
|
public void write(JsonWriter out, T value) throws IOException {
|
|
out.value("GizmoPartTypeAdapterFactory");
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public T read(JsonReader in) throws IOException {
|
|
String unused = in.nextString();
|
|
return (T) new Part("GizmoPartTypeAdapterFactory");
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
private static final class Computer {
|
|
final User user;
|
|
|
|
Computer(User user) {
|
|
this.user = user;
|
|
}
|
|
}
|
|
|
|
@JsonAdapter(UserClassAnnotationAdapter.class)
|
|
private static class User {
|
|
public final String name;
|
|
|
|
private User(String name) {
|
|
this.name = name;
|
|
}
|
|
}
|
|
|
|
private static class UserClassAnnotationAdapter extends TypeAdapter<User> {
|
|
@Override
|
|
public void write(JsonWriter out, User user) throws IOException {
|
|
out.value("UserClassAnnotationAdapter");
|
|
}
|
|
|
|
@Override
|
|
public User read(JsonReader in) throws IOException {
|
|
String unused = in.nextString();
|
|
return new User("UserClassAnnotationAdapter");
|
|
}
|
|
}
|
|
|
|
private static final class Computer2 {
|
|
// overrides the JsonAdapter annotation of User with this
|
|
@JsonAdapter(UserFieldAnnotationAdapter.class)
|
|
final User user;
|
|
|
|
Computer2(User user) {
|
|
this.user = user;
|
|
}
|
|
}
|
|
|
|
private static final class UserFieldAnnotationAdapter extends TypeAdapter<User> {
|
|
@Override
|
|
public void write(JsonWriter out, User user) throws IOException {
|
|
out.value("UserFieldAnnotationAdapter");
|
|
}
|
|
|
|
@Override
|
|
public User read(JsonReader in) throws IOException {
|
|
String unused = in.nextString();
|
|
return new User("UserFieldAnnotationAdapter");
|
|
}
|
|
}
|
|
|
|
private static final class RegisteredUserAdapter extends TypeAdapter<User> {
|
|
@Override
|
|
public void write(JsonWriter out, User user) throws IOException {
|
|
out.value("RegisteredUserAdapter");
|
|
}
|
|
|
|
@Override
|
|
public User read(JsonReader in) throws IOException {
|
|
String unused = in.nextString();
|
|
return new User("RegisteredUserAdapter");
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testJsonAdapterInvokedOnlyForAnnotatedFields() {
|
|
Gson gson = new Gson();
|
|
String json = "{'part1':'name','part2':{'name':'name2'}}";
|
|
GadgetWithTwoParts gadget = gson.fromJson(json, GadgetWithTwoParts.class);
|
|
assertThat(gadget.part1.name).isEqualTo("PartJsonFieldAnnotationAdapter");
|
|
assertThat(gadget.part2.name).isEqualTo("name2");
|
|
}
|
|
|
|
private static final class GadgetWithTwoParts {
|
|
@JsonAdapter(PartJsonFieldAnnotationAdapter.class)
|
|
final Part part1;
|
|
|
|
final Part part2; // Doesn't have the JsonAdapter annotation
|
|
|
|
@SuppressWarnings("unused")
|
|
GadgetWithTwoParts(Part part1, Part part2) {
|
|
this.part1 = part1;
|
|
this.part2 = part2;
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testJsonAdapterWrappedInNullSafeAsRequested() {
|
|
Gson gson = new Gson();
|
|
String fromJson = "{'part':null}";
|
|
|
|
GadgetWithOptionalPart gadget = gson.fromJson(fromJson, GadgetWithOptionalPart.class);
|
|
assertThat(gadget.part).isNull();
|
|
|
|
String toJson = gson.toJson(gadget);
|
|
assertThat(toJson).doesNotContain("PartJsonFieldAnnotationAdapter");
|
|
}
|
|
|
|
private static final class GadgetWithOptionalPart {
|
|
@JsonAdapter(value = PartJsonFieldAnnotationAdapter.class)
|
|
final Part part;
|
|
|
|
private GadgetWithOptionalPart(Part part) {
|
|
this.part = part;
|
|
}
|
|
}
|
|
|
|
/** Regression test contributed through https://github.com/google/gson/issues/831 */
|
|
@Test
|
|
public void testNonPrimitiveFieldAnnotationTakesPrecedenceOverDefault() {
|
|
Gson gson = new Gson();
|
|
String json = gson.toJson(new GadgetWithOptionalPart(new Part("foo")));
|
|
assertThat(json).isEqualTo("{\"part\":\"PartJsonFieldAnnotationAdapter\"}");
|
|
GadgetWithOptionalPart gadget = gson.fromJson("{'part':'foo'}", GadgetWithOptionalPart.class);
|
|
assertThat(gadget.part.name).isEqualTo("PartJsonFieldAnnotationAdapter");
|
|
}
|
|
|
|
/** Regression test contributed through https://github.com/google/gson/issues/831 */
|
|
@Test
|
|
public void testPrimitiveFieldAnnotationTakesPrecedenceOverDefault() {
|
|
Gson gson = new Gson();
|
|
String json = gson.toJson(new GadgetWithPrimitivePart(42));
|
|
assertThat(json).isEqualTo("{\"part\":\"42\"}");
|
|
GadgetWithPrimitivePart gadget = gson.fromJson(json, GadgetWithPrimitivePart.class);
|
|
assertThat(gadget.part).isEqualTo(42);
|
|
}
|
|
|
|
private static final class GadgetWithPrimitivePart {
|
|
@JsonAdapter(LongToStringTypeAdapterFactory.class)
|
|
final long part;
|
|
|
|
private GadgetWithPrimitivePart(long part) {
|
|
this.part = part;
|
|
}
|
|
}
|
|
|
|
private static final class LongToStringTypeAdapterFactory implements TypeAdapterFactory {
|
|
static final TypeAdapter<Long> ADAPTER =
|
|
new TypeAdapter<Long>() {
|
|
@Override
|
|
public void write(JsonWriter out, Long value) throws IOException {
|
|
out.value(value.toString());
|
|
}
|
|
|
|
@Override
|
|
public Long read(JsonReader in) throws IOException {
|
|
return in.nextLong();
|
|
}
|
|
};
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
|
|
Class<?> cls = type.getRawType();
|
|
if (Long.class.isAssignableFrom(cls)) {
|
|
return (TypeAdapter<T>) ADAPTER;
|
|
} else if (long.class.isAssignableFrom(cls)) {
|
|
return (TypeAdapter<T>) ADAPTER;
|
|
}
|
|
throw new IllegalStateException(
|
|
"Non-long field of type "
|
|
+ type
|
|
+ " annotated with @JsonAdapter(LongToStringTypeAdapterFactory.class)");
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testFieldAnnotationWorksForParameterizedType() {
|
|
Gson gson = new Gson();
|
|
String json = gson.toJson(new Gizmo2(Arrays.asList(new Part("Part"))));
|
|
assertThat(json).isEqualTo("{\"part\":\"GizmoPartTypeAdapterFactory\"}");
|
|
Gizmo2 computer = gson.fromJson("{'part':'Part'}", Gizmo2.class);
|
|
assertThat(computer.part.get(0).name).isEqualTo("GizmoPartTypeAdapterFactory");
|
|
}
|
|
|
|
private static final class Gizmo2 {
|
|
@JsonAdapter(Gizmo2PartTypeAdapterFactory.class)
|
|
List<Part> part;
|
|
|
|
Gizmo2(List<Part> part) {
|
|
this.part = part;
|
|
}
|
|
}
|
|
|
|
private static class Gizmo2PartTypeAdapterFactory implements TypeAdapterFactory {
|
|
@Override
|
|
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
|
|
return new TypeAdapter<T>() {
|
|
@Override
|
|
public void write(JsonWriter out, T value) throws IOException {
|
|
out.value("GizmoPartTypeAdapterFactory");
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public T read(JsonReader in) throws IOException {
|
|
String unused = in.nextString();
|
|
return (T) Arrays.asList(new Part("GizmoPartTypeAdapterFactory"));
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that {@link JsonAdapter} annotation can overwrite adapters which can normally not be
|
|
* overwritten (in this case adapter for {@link JsonElement}).
|
|
*/
|
|
@Test
|
|
public void testOverwriteBuiltIn() {
|
|
BuiltInOverwriting obj = new BuiltInOverwriting();
|
|
obj.f = new JsonPrimitive(true);
|
|
String json = new Gson().toJson(obj);
|
|
assertThat(json).isEqualTo("{\"f\":\"" + JsonElementAdapter.SERIALIZED + "\"}");
|
|
|
|
BuiltInOverwriting deserialized = new Gson().fromJson("{\"f\": 2}", BuiltInOverwriting.class);
|
|
assertThat(deserialized.f).isEqualTo(JsonElementAdapter.DESERIALIZED);
|
|
}
|
|
|
|
private static class BuiltInOverwriting {
|
|
@JsonAdapter(JsonElementAdapter.class)
|
|
JsonElement f;
|
|
}
|
|
|
|
private static class JsonElementAdapter extends TypeAdapter<JsonElement> {
|
|
static final JsonPrimitive DESERIALIZED = new JsonPrimitive("deserialized hardcoded");
|
|
|
|
@Override
|
|
public JsonElement read(JsonReader in) throws IOException {
|
|
in.skipValue();
|
|
return DESERIALIZED;
|
|
}
|
|
|
|
static final String SERIALIZED = "serialized hardcoded";
|
|
|
|
@Override
|
|
public void write(JsonWriter out, JsonElement value) throws IOException {
|
|
out.value(SERIALIZED);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify that exclusion strategy preventing serialization has higher precedence than {@link
|
|
* JsonAdapter} annotation.
|
|
*/
|
|
@Test
|
|
public void testExcludeSerializePrecedence() {
|
|
Gson gson =
|
|
new GsonBuilder()
|
|
.addSerializationExclusionStrategy(
|
|
new ExclusionStrategy() {
|
|
@Override
|
|
public boolean shouldSkipField(FieldAttributes f) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldSkipClass(Class<?> clazz) {
|
|
return false;
|
|
}
|
|
})
|
|
.create();
|
|
|
|
DelegatingAndOverwriting obj = new DelegatingAndOverwriting();
|
|
obj.f = 1;
|
|
obj.f2 = new JsonPrimitive(2);
|
|
obj.f3 = new JsonPrimitive(true);
|
|
String json = gson.toJson(obj);
|
|
assertThat(json).isEqualTo("{}");
|
|
|
|
DelegatingAndOverwriting deserialized =
|
|
gson.fromJson("{\"f\":1,\"f2\":2,\"f3\":3}", DelegatingAndOverwriting.class);
|
|
assertThat(deserialized.f).isEqualTo(Integer.valueOf(1));
|
|
assertThat(deserialized.f2).isEqualTo(new JsonPrimitive(2));
|
|
// Verify that for deserialization type adapter specified by @JsonAdapter is used
|
|
assertThat(deserialized.f3).isEqualTo(JsonElementAdapter.DESERIALIZED);
|
|
}
|
|
|
|
/**
|
|
* Verify that exclusion strategy preventing deserialization has higher precedence than {@link
|
|
* JsonAdapter} annotation.
|
|
*/
|
|
@Test
|
|
public void testExcludeDeserializePrecedence() {
|
|
Gson gson =
|
|
new GsonBuilder()
|
|
.addDeserializationExclusionStrategy(
|
|
new ExclusionStrategy() {
|
|
@Override
|
|
public boolean shouldSkipField(FieldAttributes f) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldSkipClass(Class<?> clazz) {
|
|
return false;
|
|
}
|
|
})
|
|
.create();
|
|
|
|
DelegatingAndOverwriting obj = new DelegatingAndOverwriting();
|
|
obj.f = 1;
|
|
obj.f2 = new JsonPrimitive(2);
|
|
obj.f3 = new JsonPrimitive(true);
|
|
String json = gson.toJson(obj);
|
|
// Verify that for serialization type adapters specified by @JsonAdapter are used
|
|
assertThat(json)
|
|
.isEqualTo("{\"f\":1,\"f2\":2,\"f3\":\"" + JsonElementAdapter.SERIALIZED + "\"}");
|
|
|
|
DelegatingAndOverwriting deserialized =
|
|
gson.fromJson("{\"f\":1,\"f2\":2,\"f3\":3}", DelegatingAndOverwriting.class);
|
|
assertThat(deserialized.f).isNull();
|
|
assertThat(deserialized.f2).isNull();
|
|
assertThat(deserialized.f3).isNull();
|
|
}
|
|
|
|
/**
|
|
* Verify that exclusion strategy preventing serialization and deserialization has higher
|
|
* precedence than {@link JsonAdapter} annotation.
|
|
*
|
|
* <p>This is a separate test method because {@link ReflectiveTypeAdapterFactory} handles this
|
|
* case differently.
|
|
*/
|
|
@Test
|
|
public void testExcludePrecedence() {
|
|
Gson gson =
|
|
new GsonBuilder()
|
|
.setExclusionStrategies(
|
|
new ExclusionStrategy() {
|
|
@Override
|
|
public boolean shouldSkipField(FieldAttributes f) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldSkipClass(Class<?> clazz) {
|
|
return false;
|
|
}
|
|
})
|
|
.create();
|
|
|
|
DelegatingAndOverwriting obj = new DelegatingAndOverwriting();
|
|
obj.f = 1;
|
|
obj.f2 = new JsonPrimitive(2);
|
|
obj.f3 = new JsonPrimitive(true);
|
|
String json = gson.toJson(obj);
|
|
assertThat(json).isEqualTo("{}");
|
|
|
|
DelegatingAndOverwriting deserialized =
|
|
gson.fromJson("{\"f\":1,\"f2\":2,\"f3\":3}", DelegatingAndOverwriting.class);
|
|
assertThat(deserialized.f).isNull();
|
|
assertThat(deserialized.f2).isNull();
|
|
assertThat(deserialized.f3).isNull();
|
|
}
|
|
|
|
private static class DelegatingAndOverwriting {
|
|
@JsonAdapter(DelegatingAdapterFactory.class)
|
|
Integer f;
|
|
|
|
@JsonAdapter(DelegatingAdapterFactory.class)
|
|
JsonElement f2;
|
|
|
|
// Also have non-delegating adapter to make tests handle both cases
|
|
@JsonAdapter(JsonElementAdapter.class)
|
|
JsonElement f3;
|
|
|
|
static class DelegatingAdapterFactory implements TypeAdapterFactory {
|
|
@Override
|
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
|
return gson.getDelegateAdapter(this, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies that {@link TypeAdapterFactory} specified by {@code @JsonAdapter} can call {@link
|
|
* Gson#getDelegateAdapter} without any issues, despite the factory not being directly registered
|
|
* on Gson.
|
|
*/
|
|
@Test
|
|
public void testDelegatingAdapterFactory() {
|
|
@SuppressWarnings("unchecked")
|
|
WithDelegatingFactory<String> deserialized =
|
|
new Gson().fromJson("{\"f\":\"test\"}", WithDelegatingFactory.class);
|
|
assertThat(deserialized.f).isEqualTo("test-custom");
|
|
|
|
deserialized =
|
|
new Gson().fromJson("{\"f\":\"test\"}", new TypeToken<WithDelegatingFactory<String>>() {});
|
|
assertThat(deserialized.f).isEqualTo("test-custom");
|
|
|
|
WithDelegatingFactory<String> serialized = new WithDelegatingFactory<>();
|
|
serialized.f = "value";
|
|
assertThat(new Gson().toJson(serialized)).isEqualTo("{\"f\":\"value-custom\"}");
|
|
}
|
|
|
|
private static class WithDelegatingFactory<T> {
|
|
// suppress Error Prone warning; should be clear that `Factory` refers to nested class
|
|
@SuppressWarnings("SameNameButDifferent")
|
|
@JsonAdapter(Factory.class)
|
|
T f;
|
|
|
|
static class Factory implements TypeAdapterFactory {
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
|
TypeAdapter<String> delegate = (TypeAdapter<String>) gson.getDelegateAdapter(this, type);
|
|
|
|
return (TypeAdapter<T>)
|
|
new TypeAdapter<String>() {
|
|
@Override
|
|
public String read(JsonReader in) throws IOException {
|
|
// Perform custom deserialization
|
|
return delegate.read(in) + "-custom";
|
|
}
|
|
|
|
@Override
|
|
public void write(JsonWriter out, String value) throws IOException {
|
|
// Perform custom serialization
|
|
delegate.write(out, value + "-custom");
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Similar to {@link #testDelegatingAdapterFactory}, except that the delegate is not looked up in
|
|
* {@code create} but instead in the adapter methods.
|
|
*/
|
|
@Test
|
|
public void testDelegatingAdapterFactory_Delayed() {
|
|
WithDelayedDelegatingFactory deserialized =
|
|
new Gson().fromJson("{\"f\":\"test\"}", WithDelayedDelegatingFactory.class);
|
|
assertThat(deserialized.f).isEqualTo("test-custom");
|
|
|
|
WithDelayedDelegatingFactory serialized = new WithDelayedDelegatingFactory();
|
|
serialized.f = "value";
|
|
assertThat(new Gson().toJson(serialized)).isEqualTo("{\"f\":\"value-custom\"}");
|
|
}
|
|
|
|
// suppress Error Prone warning; should be clear that `Factory` refers to nested class
|
|
@SuppressWarnings("SameNameButDifferent")
|
|
private static class WithDelayedDelegatingFactory {
|
|
@JsonAdapter(Factory.class)
|
|
String f;
|
|
|
|
static class Factory implements TypeAdapterFactory {
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
|
return (TypeAdapter<T>)
|
|
new TypeAdapter<String>() {
|
|
private TypeAdapter<String> delegate() {
|
|
return (TypeAdapter<String>) gson.getDelegateAdapter(Factory.this, type);
|
|
}
|
|
|
|
@Override
|
|
public String read(JsonReader in) throws IOException {
|
|
// Perform custom deserialization
|
|
return delegate().read(in) + "-custom";
|
|
}
|
|
|
|
@Override
|
|
public void write(JsonWriter out, String value) throws IOException {
|
|
// Perform custom serialization
|
|
delegate().write(out, value + "-custom");
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests usage of {@link Gson#getAdapter(TypeToken)} in the {@code create} method of the factory.
|
|
* Existing code was using that as workaround because {@link Gson#getDelegateAdapter} previously
|
|
* did not work in combination with {@code @JsonAdapter}, see
|
|
* https://github.com/google/gson/issues/1028.
|
|
*/
|
|
@Test
|
|
public void testGetAdapterDelegation() {
|
|
Gson gson = new Gson();
|
|
GetAdapterDelegation deserialized = gson.fromJson("{\"f\":\"de\"}", GetAdapterDelegation.class);
|
|
assertThat(deserialized.f).isEqualTo("de-custom");
|
|
|
|
String json = gson.toJson(new GetAdapterDelegation("se"));
|
|
assertThat(json).isEqualTo("{\"f\":\"se-custom\"}");
|
|
}
|
|
|
|
private static class GetAdapterDelegation {
|
|
// suppress Error Prone warning; should be clear that `Factory` refers to nested class
|
|
@SuppressWarnings("SameNameButDifferent")
|
|
@JsonAdapter(Factory.class)
|
|
String f;
|
|
|
|
GetAdapterDelegation(String f) {
|
|
this.f = f;
|
|
}
|
|
|
|
static class Factory implements TypeAdapterFactory {
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
|
// Uses `Gson.getAdapter` instead of `Gson.getDelegateAdapter`
|
|
TypeAdapter<String> delegate = (TypeAdapter<String>) gson.getAdapter(type);
|
|
|
|
return (TypeAdapter<T>)
|
|
new TypeAdapter<String>() {
|
|
@Override
|
|
public String read(JsonReader in) throws IOException {
|
|
return delegate.read(in) + "-custom";
|
|
}
|
|
|
|
@Override
|
|
public void write(JsonWriter out, String value) throws IOException {
|
|
delegate.write(out, value + "-custom");
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Tests usage of {@link JsonSerializer} as {@link JsonAdapter} value on a field */
|
|
@Test
|
|
public void testJsonSerializer() {
|
|
Gson gson = new Gson();
|
|
// Verify that delegate deserializer for List is used
|
|
WithJsonSerializer deserialized = gson.fromJson("{\"f\":[1,2,3]}", WithJsonSerializer.class);
|
|
assertThat(deserialized.f).isEqualTo(Arrays.asList(1, 2, 3));
|
|
|
|
String json = gson.toJson(new WithJsonSerializer());
|
|
// Uses custom serializer which always returns `true`
|
|
assertThat(json).isEqualTo("{\"f\":true}");
|
|
}
|
|
|
|
private static class WithJsonSerializer {
|
|
@JsonAdapter(Serializer.class)
|
|
List<Integer> f = Collections.emptyList();
|
|
|
|
static class Serializer implements JsonSerializer<List<Integer>> {
|
|
@Override
|
|
public JsonElement serialize(
|
|
List<Integer> src, Type typeOfSrc, JsonSerializationContext context) {
|
|
return new JsonPrimitive(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Tests usage of {@link JsonDeserializer} as {@link JsonAdapter} value on a field */
|
|
@Test
|
|
public void testJsonDeserializer() {
|
|
Gson gson = new Gson();
|
|
WithJsonDeserializer deserialized = gson.fromJson("{\"f\":[5]}", WithJsonDeserializer.class);
|
|
// Uses custom deserializer which always returns `[3, 2, 1]`
|
|
assertThat(deserialized.f).isEqualTo(Arrays.asList(3, 2, 1));
|
|
|
|
// Verify that delegate serializer for List is used
|
|
String json = gson.toJson(new WithJsonDeserializer(Arrays.asList(4, 5, 6)));
|
|
assertThat(json).isEqualTo("{\"f\":[4,5,6]}");
|
|
}
|
|
|
|
private static class WithJsonDeserializer {
|
|
@JsonAdapter(Deserializer.class)
|
|
List<Integer> f;
|
|
|
|
WithJsonDeserializer(List<Integer> f) {
|
|
this.f = f;
|
|
}
|
|
|
|
static class Deserializer implements JsonDeserializer<List<Integer>> {
|
|
@Override
|
|
public List<Integer> deserialize(
|
|
JsonElement json, Type typeOfT, JsonDeserializationContext context) {
|
|
return Arrays.asList(3, 2, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|