serialized = new WithDelegatingFactory<>("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"custom\":{\"f\":\"se\"}}}");
}
/**
* Tests behavior of {@link Gson#getDelegateAdapter} when the same instance of a factory is
* used (through {@link InstanceCreator}).
*
* Important: This situation is likely a rare corner case; the purpose of this test is
* to verify that Gson behaves reasonable, mainly that it does not cause a {@link
* StackOverflowError} due to infinite recursion. This test is not intended to dictate an expected
* behavior.
*/
@Test
public void testDelegating_SameFactoryInstance() {
WithDelegatingFactory.Factory factory = new WithDelegatingFactory.Factory();
Gson gson =
new GsonBuilder()
.registerTypeAdapterFactory(factory)
// Always provides same instance for factory
.registerTypeAdapter(
WithDelegatingFactory.Factory.class, (InstanceCreator>) type -> factory)
.create();
// Current Gson.getDelegateAdapter implementation cannot tell when call is related to
// @JsonAdapter or not, it can only work based on the `skipPast` factory, so if the same factory
// instance is used the one registered with `GsonBuilder.registerTypeAdapterFactory` actually
// skips past the @JsonAdapter one, so the JSON string is `{"custom": ...}` instead of
// `{"custom":{"custom":...}}`
WithDelegatingFactory> deserialized =
gson.fromJson("{\"custom\":{\"f\":\"de\"}}", WithDelegatingFactory.class);
assertThat(deserialized.f).isEqualTo("de");
WithDelegatingFactory serialized = new WithDelegatingFactory<>("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"f\":\"se\"}}");
}
/**
* Tests behavior of {@link Gson#getDelegateAdapter} when different instances of the same
* factory class are used; one specified with {@code @JsonAdapter} on a class, and the other
* specified with {@code @JsonAdapter} on a field of that class.
*
* Important: This situation is likely a rare corner case; the purpose of this test is
* to verify that Gson behaves reasonable, mainly that it does not cause a {@link
* StackOverflowError} due to infinite recursion. This test is not intended to dictate an expected
* behavior.
*/
@Test
public void testDelegating_SameFactoryClass_OnClassAndField() {
Gson gson =
new GsonBuilder()
.registerTypeAdapter(
String.class,
new TypeAdapter() {
@Override
public String read(JsonReader in) throws IOException {
return in.nextString() + "-str";
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value + "-str");
}
})
.create();
// Should use both factories, and therefore have `{"custom": ... }` once for class and once for
// the field, and for field also properly delegate to custom String adapter defined above
WithDelegatingFactoryOnClassAndField deserialized =
gson.fromJson(
"{\"custom\":{\"f\":{\"custom\":\"de\"}}}", WithDelegatingFactoryOnClassAndField.class);
assertThat(deserialized.f).isEqualTo("de-str");
WithDelegatingFactoryOnClassAndField serialized =
new WithDelegatingFactoryOnClassAndField("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"f\":{\"custom\":\"se-str\"}}}");
}
/**
* Tests behavior of {@link Gson#getDelegateAdapter} when the same instance of a factory is
* used (through {@link InstanceCreator}); specified with {@code @JsonAdapter} on a class, and
* also specified with {@code @JsonAdapter} on a field of that class.
*
* Important: This situation is likely a rare corner case; the purpose of this test is
* to verify that Gson behaves reasonable, mainly that it does not cause a {@link
* StackOverflowError} due to infinite recursion. This test is not intended to dictate an expected
* behavior.
*/
@Test
public void testDelegating_SameFactoryInstance_OnClassAndField() {
WithDelegatingFactoryOnClassAndField.Factory factory =
new WithDelegatingFactoryOnClassAndField.Factory();
Gson gson =
new GsonBuilder()
.registerTypeAdapter(
String.class,
new TypeAdapter() {
@Override
public String read(JsonReader in) throws IOException {
return in.nextString() + "-str";
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value + "-str");
}
})
// Always provides same instance for factory
.registerTypeAdapter(
WithDelegatingFactoryOnClassAndField.Factory.class,
(InstanceCreator>) type -> factory)
.create();
// Because field type (`String`) differs from declaring class,
// JsonAdapterAnnotationTypeAdapterFactory does not confuse factories and this behaves as
// expected: Both the declaring class and the field each have `{"custom": ...}` and delegation
// for the field to the custom String adapter defined above works properly
WithDelegatingFactoryOnClassAndField deserialized =
gson.fromJson(
"{\"custom\":{\"f\":{\"custom\":\"de\"}}}", WithDelegatingFactoryOnClassAndField.class);
assertThat(deserialized.f).isEqualTo("de-str");
WithDelegatingFactoryOnClassAndField serialized =
new WithDelegatingFactoryOnClassAndField("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"f\":{\"custom\":\"se-str\"}}}");
}
// Same factory class specified on class and one of its fields
@JsonAdapter(WithDelegatingFactoryOnClassAndField.Factory.class)
private static class WithDelegatingFactoryOnClassAndField {
// suppress Error Prone warning; should be clear that `Factory` refers to nested class
@SuppressWarnings("SameNameButDifferent")
@JsonAdapter(Factory.class)
String f;
WithDelegatingFactoryOnClassAndField(String f) {
this.f = f;
}
static class Factory implements TypeAdapterFactory {
@Override
public TypeAdapter create(Gson gson, TypeToken type) {
TypeAdapter delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter() {
@Override
public T read(JsonReader in) throws IOException {
// Perform custom deserialization
in.beginObject();
assertThat(in.nextName()).isEqualTo("custom");
T t = delegate.read(in);
in.endObject();
return t;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
// Perform custom serialization
out.beginObject();
out.name("custom");
delegate.write(out, value);
out.endObject();
}
};
}
}
}
/** Tests usage of {@link JsonSerializer} as {@link JsonAdapter} value */
@Test
public void testJsonSerializer() {
Gson gson = new Gson();
// Verify that delegate deserializer (reflection deserializer) is used
WithJsonSerializer deserialized = gson.fromJson("{\"f\":\"test\"}", WithJsonSerializer.class);
assertThat(deserialized.f).isEqualTo("test");
String json = gson.toJson(new WithJsonSerializer());
// Uses custom serializer which always returns `true`
assertThat(json).isEqualTo("true");
}
@JsonAdapter(WithJsonSerializer.Serializer.class)
private static class WithJsonSerializer {
String f = "";
static class Serializer implements JsonSerializer {
@Override
public JsonElement serialize(
WithJsonSerializer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(true);
}
}
}
/** Tests usage of {@link JsonDeserializer} as {@link JsonAdapter} value */
@Test
public void testJsonDeserializer() {
Gson gson = new Gson();
WithJsonDeserializer deserialized =
gson.fromJson("{\"f\":\"test\"}", WithJsonDeserializer.class);
// Uses custom deserializer which always uses "123" as field value
assertThat(deserialized.f).isEqualTo("123");
// Verify that delegate serializer (reflection serializer) is used
String json = gson.toJson(new WithJsonDeserializer("abc"));
assertThat(json).isEqualTo("{\"f\":\"abc\"}");
}
@JsonAdapter(WithJsonDeserializer.Deserializer.class)
private static class WithJsonDeserializer {
String f;
WithJsonDeserializer(String f) {
this.f = f;
}
static class Deserializer implements JsonDeserializer {
@Override
public WithJsonDeserializer deserialize(
JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new WithJsonDeserializer("123");
}
}
}
/**
* Tests creation of the adapter referenced by {@code @JsonAdapter} using an {@link
* InstanceCreator}.
*/
@Test
public void testAdapterCreatedByInstanceCreator() {
CreatedByInstanceCreator.Serializer serializer =
new CreatedByInstanceCreator.Serializer("custom");
Gson gson =
new GsonBuilder()
.registerTypeAdapter(
CreatedByInstanceCreator.Serializer.class, (InstanceCreator>) t -> serializer)
.create();
String json = gson.toJson(new CreatedByInstanceCreator());
assertThat(json).isEqualTo("\"custom\"");
}
@JsonAdapter(CreatedByInstanceCreator.Serializer.class)
private static class CreatedByInstanceCreator {
static class Serializer implements JsonSerializer {
private final String value;
@SuppressWarnings("unused")
public Serializer() {
throw new AssertionError("should not be called");
}
public Serializer(String value) {
this.value = value;
}
@Override
public JsonElement serialize(
CreatedByInstanceCreator src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(value);
}
}
}
/** Tests creation of the adapter referenced by {@code @JsonAdapter} using JDK Unsafe. */
@Test
public void testAdapterCreatedByJdkUnsafe() {
String json = new Gson().toJson(new CreatedByJdkUnsafe());
assertThat(json).isEqualTo("false");
}
@JsonAdapter(CreatedByJdkUnsafe.Serializer.class)
private static class CreatedByJdkUnsafe {
static class Serializer implements JsonSerializer {
// JDK Unsafe leaves this at default value `false`
private boolean wasInitialized = true;
// Explicit constructor with args to remove implicit no-args constructor
@SuppressWarnings("unused")
public Serializer(int i) {
throw new AssertionError("should not be called");
}
@Override
public JsonElement serialize(
CreatedByJdkUnsafe src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(wasInitialized);
}
}
}
}