Skip to content

Commit

Permalink
Document how JsonAdapter creates adapter instances & add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcono1234 committed Jul 21, 2023
1 parent 1513897 commit 541d138
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 8 deletions.
2 changes: 2 additions & 0 deletions gson/src/main/java/com/google/gson/InstanceCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
*
* @param <T> the type of object that will be created by this implementation.
*
* @see GsonBuilder#registerTypeAdapter(Type, Object)
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
Expand Down
36 changes: 28 additions & 8 deletions gson/src/main/java/com/google/gson/annotations/JsonAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,20 +37,22 @@
* &#64;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&lt;User&gt; {
* &#64;Override public void write(JsonWriter out, User user) throws IOException {
* // implement write: combine firstName and lastName into name
* out.beginObject();
* out.name("name");
* out.value(user.firstName + " " + user.lastName);
* out.endObject();
* // implement the write method
* }
*
* &#64;Override public User read(JsonReader in) throws IOException {
* // implement read: split name into firstName and lastName
* in.beginObject();
Expand All @@ -60,30 +64,46 @@
* }
* </pre>
*
* Since User class specified UserJsonAdapter.class in &#64;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.
*
* <p> Here is an example of how to apply this annotation to a field.
* <p>Here is an example of how to apply this annotation to a field.
* <pre>
* private static final class Gadget {
* &#64;JsonAdapter(UserJsonAdapter2.class)
* &#64;JsonAdapter(UserJsonAdapter.class)
* final User user;
*
* Gadget(User user) {
* this.user = user;
* }
* }
* </pre>
*
* 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.
*
* <p>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.
*
* <p>The type adapter is created in the same way Gson creates instances of
* custom classes during deserialization, that means:
* <ol>
* <li>If a custom {@link InstanceCreator} has been registered for the
* adapter class, it will be used to create the instance
* <li>Otherwise, if the adapter class has a no-args constructor
* (regardless of which visibility), it will be invoked to create
* the instance
* <li>Otherwise, JDK {@code Unsafe} will be used to create the instance;
* see {@link GsonBuilder#disableJdkUnsafe()} for the unexpected
* side-effects this might have
* </ol>
*
* <p>{@code Gson} instances might cache the adapter they create for
* a {@code @JsonAdapter} annotation. It is not guaranteed that a new
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CreatedByInstanceCreator> {
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<CreatedByJdkUnsafe> {
// 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);
}
}
}
}

0 comments on commit 541d138

Please sign in to comment.