问题描述
Gson 没有直接支持序列化@Autovalue 类或 Optional 字段,但是 com.ryanharter.auto.value 添加了 @Autovalue 并且 net.dongliu:gson-java8-datatype 添加了 Optional 和其他 java8 类型。
然而,他们并不一起工作 AFAICT。
测试代码:
public class TestOptionalWithAutovalue {
private static final Gson gson = new GsonBuilder().serializeNulls()
// doesnt matter which order these are registered in
.registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory())
.registerTypeAdapterFactory(AutovalueGsonTypeAdapterFactory.create())
.create();
@Test
public void testAutovalueOptionalEmpty() {
AVotestClass subject = AVotestClass.create(Optional.empty());
String json = gson.toJson(subject,AVotestClass.class);
System.out.printf("Json produced = %s%n",json);
AVotestClass back = gson.fromJson(json,new Typetoken<AVotestClass>() {}.getType());
assertthat(back).isEqualTo(subject);
}
@Test
public void testAutovalueOptionalFull() {
AVotestClass subject = AVotestClass.create(Optional.of("ok"));
String json = gson.toJson(subject,AVotestClass.class);
System.out.printf("Json produced = '%s'%n",new Typetoken<AVotestClass>() {}.getType());
assertthat(back).isEqualTo(subject);
}
}
@Autovalue
public abstract class AVotestClass {
abstract Optional<String> sval();
public static AVotestClass create(Optional<String> sval) {
return new Autovalue_AVotestClass(sval);
}
public static TypeAdapter<AVotestClass> typeAdapter(Gson gson) {
return new Autovalue_AVotestClass.GsonTypeAdapter(gson);
}
}
@GsonTypeAdapterFactory
public abstract class AutovalueGsonTypeAdapterFactory implements TypeAdapterFactory {
public static TypeAdapterFactory create() {
return new AutovalueGson_AutovalueGsonTypeAdapterFactory();
}
}
gradle 依赖:
annotationProcessor "com.google.auto.value:auto-value:1.7.4"
annotationProcessor("com.ryanharter.auto.value:auto-value-gson-extension:1.3.1")
implementation("com.ryanharter.auto.value:auto-value-gson-runtime:1.3.1")
annotationProcessor("com.ryanharter.auto.value:auto-value-gson-factory:1.3.1")
implementation 'net.dongliu:gson-java8-datatype:1.1.0'
失败:
Json produced = {"sval":null}
...
java.lang.NullPointerException: Null sval
...
net.dongliu.gson.OptionalAdapter 在序列化时调用,而不是反序列化。
我想知道是否有解决方法,或者答案是否是 Gson 需要直接支持 Optional ?
解决方法
很高兴看到您通过添加更多信息甚至添加测试来更新您的问题! :) 这真的很清楚!
我不确定,但生成的类型适配器没有提及 sval
的默认值:
jsonReader.beginObject();
// [NOTE] This is where it is initialized with null,so I guess it will definitely fail if the `sval` property is not even present in the deserialized JSON object
Optional<String> sval = null;
while (jsonReader.hasNext()) {
String _name = jsonReader.nextName();
// [NOTE] This is where it skips `null` value so it even does not reach to the `OptionalAdapter` run
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
continue;
}
switch (_name) {
default: {
if ("sval".equals(_name)) {
TypeAdapter<Optional<String>> optional__string_adapter = this.optional__string_adapter;
if (optional__string_adapter == null) {
this.optional__string_adapter = optional__string_adapter = (TypeAdapter<Optional<String>>) gson.getAdapter(TypeToken.getParameterized(Optional.class,String.class));
}
sval = optional__string_adapter.read(jsonReader);
continue;
}
jsonReader.skipValue();
}
}
}
jsonReader.endObject();
return new AutoValue_AvoTestClass(sval);
我不知道是否有办法在 AutoValue 或您提到的其他生成器中配置默认值,但它看起来像是一个错误。
如果没有任何方法可以解决它(例如,放弃了库开发;等待修复需要花费太多时间;无论如何),您总是可以自己实现它,但是需要一些运行时成本(基本上这就是如何Gson 在后台为数据包对象工作)。
这个想法是将工作委托给内置的 RuntimeTypeAdapterFactory
以便它可以处理具体类,而不是抽象类,并根据注册的类型适配器设置所有字段(以便支持 Java 8 类型同样)。
这里的成本是反射,因此适配器可能比生成类型适配器工作得慢。
另一件事是,如果在 JSON 对象中甚至没有遇到 JSON 属性,则对应的字段将保持 null
。
这需要另一个反序列化后类型的适配器。
final class SubstitutionTypeAdapterFactory
implements TypeAdapterFactory {
private final Function<? super Type,? extends Type> substitute;
private SubstitutionTypeAdapterFactory(final Function<? super Type,? extends Type> substitute) {
this.substitute = substitute;
}
static TypeAdapterFactory create(final Function<? super Type,? extends Type> substitute) {
return new SubstitutionTypeAdapterFactory(substitute);
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson,final TypeToken<T> typeToken) {
@Nullable
final Type substitution = substitute.apply(typeToken.getType());
if ( substitution == null ) {
return null;
}
@SuppressWarnings("unchecked")
final TypeAdapter<T> delegateTypeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this,TypeToken.get(substitution));
return delegateTypeAdapter;
}
}
final class DefaultsTypeAdapterFactory
implements TypeAdapterFactory {
private final Function<? super Type,? extends Type> substitute;
private final LoadingCache<Class<?>,Collection<Map.Entry<Field,?>>> fieldsCache;
private DefaultsTypeAdapterFactory(final Function<? super Type,? extends Type> substitute,final Function<? super Type,?> toDefault) {
this.substitute = substitute;
fieldsCache = CacheBuilder.newBuilder()
// TODO tweak the cache
.build(new CacheLoader<Class<?>,?>>>() {
@Override
public Collection<Map.Entry<Field,?>> load(final Class<?> clazz) {
// TODO walk hieararchy
return Stream.of(clazz.getDeclaredFields())
.map(field -> {
@Nullable
final Object defaultValue = toDefault.apply(field.getGenericType());
if ( defaultValue == null ) {
return null;
}
field.setAccessible(true);
return new AbstractMap.SimpleImmutableEntry<>(field,defaultValue);
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
});
}
static TypeAdapterFactory create(final Function<? super Type,?> toDefault) {
return new DefaultsTypeAdapterFactory(substitute,toDefault);
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson,final TypeToken<T> typeToken) {
@Nullable
final Type substitution = substitute.apply(typeToken.getType());
if ( substitution == null ) {
return null;
}
if ( !(substitution instanceof Class) ) {
return null;
}
final Collection<Map.Entry<Field,?>> fieldsToPatch = fieldsCache.getUnchecked((Class<?>) substitution);
if ( fieldsToPatch.isEmpty() ) {
return null;
}
final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this,typeToken);
return new TypeAdapter<T>() {
@Override
public void write(final JsonWriter out,final T value)
throws IOException {
delegateTypeAdapter.write(out,value);
}
@Override
public T read(final JsonReader in)
throws IOException {
final T value = delegateTypeAdapter.read(in);
for ( final Map.Entry<Field,?> e : fieldsToPatch ) {
final Field field = e.getKey();
final Object defaultValue = e.getValue();
try {
if ( field.get(value) == null ) {
field.set(value,defaultValue);
}
} catch ( final IllegalAccessException ex ) {
throw new RuntimeException(ex);
}
}
return value;
}
};
}
}
@AutoValue
abstract class AvoTestClass {
abstract Optional<String> sval();
static AvoTestClass create(final Optional<String> sval) {
return new AutoValue_AvoTestClass(sval);
}
static Class<? extends AvoTestClass> type() {
return AutoValue_AvoTestClass.class;
}
}
public final class OptionalWithAutoValueTest {
private static final Map<Type,Type> autoValueClasses = ImmutableMap.<Type,Type>builder()
.put(AvoTestClass.class,AvoTestClass.type())
.build();
private static final Map<Class<?>,?> defaultValues = ImmutableMap.<Class<?>,Object>builder()
.put(Optional.class,Optional.empty())
.build();
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new GsonJava8TypeAdapterFactory())
.registerTypeAdapterFactory(SubstitutionTypeAdapterFactory.create(autoValueClasses::get))
.registerTypeAdapterFactory(DefaultsTypeAdapterFactory.create(autoValueClasses::get,type -> {
if ( type instanceof Class ) {
return defaultValues.get(type);
}
if ( type instanceof ParameterizedType ) {
return defaultValues.get(((ParameterizedType) type).getRawType());
}
return null;
}))
.create();
@SuppressWarnings("unused")
private static Stream<Optional<String>> test() {
return Stream.of(
Optional.of("ok"),Optional.empty()
);
}
@ParameterizedTest
@MethodSource
public void test(final Optional<String> optional) {
final AvoTestClass before = AvoTestClass.create(optional);
final String json = gson.toJson(before,AvoTestClass.class);
final AvoTestClass after = gson.fromJson(json,AvoTestClass.class);
Assert.assertEquals(before,after);
}
}
此解决方案严重基于反射,但如果生成器无法完成这项工作,它只是一种解决方法(同样,不确定是否可以对其进行配置,以免出现此类问题)。