From 541d13828751c84610a0c2d86814c6f0ad9054c1 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 22 Jul 2023 00:25:33 +0200 Subject: [PATCH] Document how `JsonAdapter` creates adapter instances & add tests --- .../java/com/google/gson/InstanceCreator.java | 2 + .../google/gson/annotations/JsonAdapter.java | 36 ++++++++--- .../JsonAdapterAnnotationOnClassesTest.java | 61 +++++++++++++++++++ 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/gson/src/main/java/com/google/gson/InstanceCreator.java b/gson/src/main/java/com/google/gson/InstanceCreator.java index b973da07eb..486f250482 100644 --- a/gson/src/main/java/com/google/gson/InstanceCreator.java +++ b/gson/src/main/java/com/google/gson/InstanceCreator.java @@ -73,6 +73,8 @@ * * @param the type of object that will be created by this implementation. * + * @see GsonBuilder#registerTypeAdapter(Type, Object) + * * @author Inderjeet Singh * @author Joel Leitch */ diff --git a/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java b/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java index d168575940..295be95a2c 100644 --- a/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java +++ b/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java @@ -17,6 +17,8 @@ package com.google.gson.annotations; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.InstanceCreator; import com.google.gson.JsonDeserializer; import com.google.gson.JsonSerializer; import com.google.gson.TypeAdapter; @@ -35,11 +37,13 @@ * @JsonAdapter(UserJsonAdapter.class) * public class User { * public final String firstName, lastName; + * * private User(String firstName, String lastName) { * this.firstName = firstName; * this.lastName = lastName; * } * } + * * public class UserJsonAdapter extends TypeAdapter<User> { * @Override public void write(JsonWriter out, User user) throws IOException { * // implement write: combine firstName and lastName into name @@ -47,8 +51,8 @@ * out.name("name"); * out.value(user.firstName + " " + user.lastName); * out.endObject(); - * // implement the write method * } + * * @Override public User read(JsonReader in) throws IOException { * // implement read: split name into firstName and lastName * in.beginObject(); @@ -60,14 +64,15 @@ * } * * - * Since User class specified UserJsonAdapter.class in @JsonAdapter annotation, it - * will automatically be invoked to serialize/deserialize User instances. + * Since {@code User} class specified {@code UserJsonAdapter.class} in {@code @JsonAdapter} + * annotation, it will automatically be invoked to serialize/deserialize {@code User} instances. * - *

Here is an example of how to apply this annotation to a field. + *

Here is an example of how to apply this annotation to a field. *

  * private static final class Gadget {
- *   @JsonAdapter(UserJsonAdapter2.class)
+ *   @JsonAdapter(UserJsonAdapter.class)
  *   final User user;
+ *
  *   Gadget(User user) {
  *     this.user = user;
  *   }
@@ -75,15 +80,30 @@
  * 
* * It's possible to specify different type adapters on a field, that - * field's type, and in the {@link com.google.gson.GsonBuilder}. Field - * annotations take precedence over {@code GsonBuilder}-registered type + * field's type, and in the {@link GsonBuilder}. Field annotations + * take precedence over {@code GsonBuilder}-registered type * adapters, which in turn take precedence over annotated types. * *

The class referenced by this annotation must be either a {@link * TypeAdapter} or a {@link TypeAdapterFactory}, or must implement one * or both of {@link JsonDeserializer} or {@link JsonSerializer}. * Using {@link TypeAdapterFactory} makes it possible to delegate - * to the enclosing {@link Gson} instance. + * to the enclosing {@link Gson} instance. By default the specified + * adapter will not be called for {@code null} values; set {@link #nullSafe()} + * to {@code false} to let the adapter handle {@code null} values itself. + * + *

The type adapter is created in the same way Gson creates instances of + * custom classes during deserialization, that means: + *

    + *
  1. If a custom {@link InstanceCreator} has been registered for the + * adapter class, it will be used to create the instance + *
  2. Otherwise, if the adapter class has a no-args constructor + * (regardless of which visibility), it will be invoked to create + * the instance + *
  3. Otherwise, JDK {@code Unsafe} will be used to create the instance; + * see {@link GsonBuilder#disableJdkUnsafe()} for the unexpected + * side-effects this might have + *
* *

{@code Gson} instances might cache the adapter they create for * a {@code @JsonAdapter} annotation. It is not guaranteed that a new diff --git a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java index e5aa70f4f0..f4af119883 100644 --- a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java +++ b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java @@ -602,4 +602,65 @@ public WithJsonDeserializer deserialize(JsonElement json, Type typeOfT, JsonDese } } } + + /** + * 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); + } + } + } }