Fixed nullSafe usage. (#1555)

The JsonSerializer/Deserializer adapters used to ignore this attribute
which result in inconsistent behaviour for annotated adapters.

Fixes #1553

Signed-off-by: Dmitry Bufistov <dmitry@midokura.com>

Co-authored-by: Dmitry Bufistov <dmitry@midokura.com>
This commit is contained in:
bufistov 2022-08-05 16:33:05 +02:00 committed by GitHub
parent 98f2bbf4c1
commit 46b97bf156
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 32 additions and 5 deletions

View File

@ -55,6 +55,7 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
Object instance = constructorConstructor.get(TypeToken.get(annotation.value())).construct(); Object instance = constructorConstructor.get(TypeToken.get(annotation.value())).construct();
TypeAdapter<?> typeAdapter; TypeAdapter<?> typeAdapter;
boolean nullSafe = annotation.nullSafe();
if (instance instanceof TypeAdapter) { if (instance instanceof TypeAdapter) {
typeAdapter = (TypeAdapter<?>) instance; typeAdapter = (TypeAdapter<?>) instance;
} else if (instance instanceof TypeAdapterFactory) { } else if (instance instanceof TypeAdapterFactory) {
@ -66,7 +67,8 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
JsonDeserializer<?> deserializer = instance instanceof JsonDeserializer JsonDeserializer<?> deserializer = instance instanceof JsonDeserializer
? (JsonDeserializer) instance ? (JsonDeserializer) instance
: null; : null;
typeAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null); typeAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null, nullSafe);
nullSafe = false;
} else { } else {
throw new IllegalArgumentException("Invalid attempt to bind an instance of " throw new IllegalArgumentException("Invalid attempt to bind an instance of "
+ instance.getClass().getName() + " as a @JsonAdapter for " + type.toString() + instance.getClass().getName() + " as a @JsonAdapter for " + type.toString()
@ -74,7 +76,7 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
+ " JsonSerializer or JsonDeserializer."); + " JsonSerializer or JsonDeserializer.");
} }
if (typeAdapter != null && annotation.nullSafe()) { if (typeAdapter != null && nullSafe) {
typeAdapter = typeAdapter.nullSafe(); typeAdapter = typeAdapter.nullSafe();
} }

View File

@ -45,17 +45,24 @@ public final class TreeTypeAdapter<T> extends TypeAdapter<T> {
private final TypeToken<T> typeToken; private final TypeToken<T> typeToken;
private final TypeAdapterFactory skipPast; private final TypeAdapterFactory skipPast;
private final GsonContextImpl context = new GsonContextImpl(); private final GsonContextImpl context = new GsonContextImpl();
private final boolean nullSafe;
/** The delegate is lazily created because it may not be needed, and creating it may fail. */ /** The delegate is lazily created because it may not be needed, and creating it may fail. */
private volatile TypeAdapter<T> delegate; private volatile TypeAdapter<T> delegate;
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer, public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) { Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast, boolean nullSafe) {
this.serializer = serializer; this.serializer = serializer;
this.deserializer = deserializer; this.deserializer = deserializer;
this.gson = gson; this.gson = gson;
this.typeToken = typeToken; this.typeToken = typeToken;
this.skipPast = skipPast; this.skipPast = skipPast;
this.nullSafe = nullSafe;
}
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) {
this(serializer, deserializer, gson, typeToken, skipPast, true);
} }
@Override public T read(JsonReader in) throws IOException { @Override public T read(JsonReader in) throws IOException {
@ -63,7 +70,7 @@ public final class TreeTypeAdapter<T> extends TypeAdapter<T> {
return delegate().read(in); return delegate().read(in);
} }
JsonElement value = Streams.parse(in); JsonElement value = Streams.parse(in);
if (value.isJsonNull()) { if (nullSafe && value.isJsonNull()) {
return null; return null;
} }
return deserializer.deserialize(value, typeToken.getType(), context); return deserializer.deserialize(value, typeToken.getType(), context);
@ -74,7 +81,7 @@ public final class TreeTypeAdapter<T> extends TypeAdapter<T> {
delegate().write(out, value); delegate().write(out, value);
return; return;
} }
if (value == null) { if (nullSafe && value == null) {
out.nullValue(); out.nullValue();
return; return;
} }

View File

@ -161,4 +161,22 @@ public final class JsonAdapterSerializerDeserializerTest extends TestCase {
return new JsonPrimitive("BaseIntegerAdapter"); return new JsonPrimitive("BaseIntegerAdapter");
} }
} }
public void testJsonAdapterNullSafe() {
Gson gson = new Gson();
String json = gson.toJson(new Computer3(null, null));
assertEquals("{\"user1\":\"UserSerializerDeserializer\"}", json);
Computer3 computer3 = gson.fromJson("{\"user1\":null, \"user2\":null}", Computer3.class);
assertEquals("UserSerializerDeserializer", computer3.user1.name);
assertNull(computer3.user2);
}
private static final class Computer3 {
@JsonAdapter(value = UserSerializerDeserializer.class, nullSafe = false) final User user1;
@JsonAdapter(value = UserSerializerDeserializer.class) final User user2;
Computer3(User user1, User user2) {
this.user1 = user1;
this.user2 = user2;
}
}
} }