很高兴看到您通过添加更多信息甚至添加测试来更新您的问题! :) 这真的很清楚!
我不确定,但生成的类型适配器没有提及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<?>, Collection<Map.Entry<Field, ?>>>() {
@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, ? extends Type> substitute, 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);
}
}
此解决方案严重基于反射,但如果生成器无法完成工作,它只是一种解决方法(再次,不确定它们是否可以配置为不存在此类问题)。