Gson @AutoValue 和 Optional<> 不能一起工作,有解决方法吗?

问题描述

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);
    }

}

此解决方案严重基于反射,但如果生成器无法完成这项工作,它只是一种解决方法(同样,不确定是否可以对其进行配置,以免出现此类问题)。