【问题标题】:Gson @AutoValue and Optional<> dont work together, is there a workaround?Gson @AutoValue 和 Optional<> 不能一起工作,有解决方法吗?
【发布时间】:2021-04-07 04:15:17
【问题描述】:

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", json);
    AvoTestClass back = gson.fromJson(json, 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 ?

【问题讨论】:

    标签: java gson optional auto-value


    【解决方案1】:

    很高兴看到您通过添加更多信息甚至添加测试来更新您的问题! :) 这真的很清楚!

    我不确定,但生成的类型适配器没有提及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);
        }
    
    }
    

    此解决方案严重基于反射,但如果生成器无法完成工作,它只是一种解决方法(再次,不确定它们是否可以配置为不存在此类问题)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-02
      • 1970-01-01
      • 1970-01-01
      • 2022-08-15
      • 1970-01-01
      • 2018-01-18
      • 1970-01-01
      相关资源
      最近更新 更多