簡體   English   中英

基於GSON中字段名的同類型序列化不帶注解

[英]Same type serialization based on field name in GSON without annotations

給定一個我無法修改的 class

class ThirdPartyDTO
{
  Instant foo;
  Instant bar;
  // many more fields.
}

我有 class 的 JSON 表示,它使用兩種不同的模式來表示 foo 和 bar。

如果字段名稱為 foo,則使用此模式,如果字段名稱為 bar,則使用其他模式。

如何使用 gson 做到這一點而不在每個字段名稱上添加(因為我不能)注釋?

謝謝。

因此,正如我在上面的評論中提到的,Gson 類型的適配器無法訪問它們序列化或反序列化的對象的完整上下文。 例如,單個類型(層次結構)的類型適配器並不真正知道它可能應用於哪個字段(這就是帖子中的問題)。 為了對不同的字段應用不同的類型適配器,可以使用JsonSerializerJsonDeserializer (因此每個字段都必須手動處理,這是一項繁瑣的工作)。 這里的另一個壞事是,應該像這樣處理 DTO 的ReflectiveTypeAdapterFactory不能直接擴展,而只能通過同樣有限的GsonBuilder接口進行擴展。

但是,可以實現使用以下算法的解決方法:

  • 創建一個排除策略,總是在反序列化時跳過特殊字段(這僅影響ReflectiveTypeAdapterFactory );
  • 創建一個類型適配器工廠,為這些特殊字段創建類型適配器;
  • once Gson deserializes the wrapper object, the special fields in the wrapper object are supposed to be skipped but set to null (other other defaults in case of primitives), the post-deserializer type adapter asks injected strategies to deserialize each special field that was previously被排除策略跳過,因此ReflectiveTypeAdapterFactory

這就是訣竅。

interface IPostPatchFactory {

    @Nonnull
    TypeAdapterFactory createTypeAdapterFactory();

    @Nonnull
    ExclusionStrategy createExclusionStrategy();

}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
final class PostPatchFactory
        implements IPostPatchFactory {

    private final Predicate<? super FieldDatum> isFieldPostPatched;
    private final Predicate<? super Class<?>> isClassPostPatched;
    private final Iterable<FieldPatch<?>> fieldPatches;

    static IPostPatchFactory create(final Collection<FieldPatch<?>> fieldPatches) {
        final Collection<FieldPatch<?>> fieldPatchesCopy = new ArrayList<>(fieldPatches);
        final Collection<Field> postPatchedFields = fieldPatches.stream()
                .map(FieldPatch::getField)
                .collect(Collectors.toList());
        final Collection<FieldDatum> postPatchedFieldAttributes = postPatchedFields.stream()
                .map(FieldDatum::from)
                .collect(Collectors.toList());
        final Collection<? super Class<?>> isClassPostPatched = postPatchedFieldAttributes.stream()
                .map(fieldDatum -> fieldDatum.declaringClass)
                .collect(Collectors.toList());
        return new PostPatchFactory(postPatchedFieldAttributes::contains, isClassPostPatched::contains, fieldPatchesCopy);
    }

    @Nonnull
    @Override
    public TypeAdapterFactory createTypeAdapterFactory() {
        return new PostPatchTypeAdapterFactory(isClassPostPatched, fieldPatches);
    }

    @Nonnull
    @Override
    public ExclusionStrategy createExclusionStrategy() {
        return new PostPatchExclusionStrategy(isFieldPostPatched);
    }

    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    private static final class PostPatchTypeAdapterFactory
            implements TypeAdapterFactory {

        private final Predicate<? super Class<?>> isClassPostPatched;
        private final Iterable<FieldPatch<?>> fieldPatches;

        @Override
        @Nullable
        public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
            final Class<? super T> rawType = typeToken.getRawType();
            if ( !isClassPostPatched.test(rawType) ) {
                return null;
            }
            return new PostPatchTypeAdapter<>(gson, gson.getDelegateAdapter(this, typeToken), fieldPatches)
                    .nullSafe();
        }

        @AllArgsConstructor(access = AccessLevel.PRIVATE)
        private static final class PostPatchTypeAdapter<T>
                extends TypeAdapter<T> {

            private final Gson gson;
            private final TypeAdapter<T> delegateTypeAdapter;
            private final Iterable<FieldPatch<?>> fieldPatches;

            @Override
            public void write(final JsonWriter out, final T value) {
                throw new UnsupportedOperationException("TODO");
            }

            @Override
            public T read(final JsonReader in) {
                final JsonElement bufferedJsonElement = JsonParser.parseReader(in);
                final T value = delegateTypeAdapter.fromJsonTree(bufferedJsonElement);
                for ( final FieldPatch<?> fieldPatch : fieldPatches ) {
                    final Field field = fieldPatch.getField();
                    final BiFunction<? super Gson, ? super JsonElement, ?> deserialize = fieldPatch.getDeserialize();
                    final Object fieldValue = deserialize.apply(gson, bufferedJsonElement);
                    try {
                        field.set(value, fieldValue);
                    } catch ( final IllegalAccessException ex ) {
                        throw new RuntimeException(ex);
                    }
                }
                return value;
            }

        }

    }

    private static final class PostPatchExclusionStrategy
            implements ExclusionStrategy {

        private final Predicate<? super FieldDatum> isFieldPostPatched;

        private PostPatchExclusionStrategy(final Predicate<? super FieldDatum> isFieldPostPatched) {
            this.isFieldPostPatched = isFieldPostPatched;
        }

        @Override
        public boolean shouldSkipField(final FieldAttributes fieldAttributes) {
            return isFieldPostPatched.test(FieldDatum.from(fieldAttributes));
        }

        @Override
        public boolean shouldSkipClass(final Class<?> clazz) {
            return false;
        }

    }

    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    @EqualsAndHashCode
    private static final class FieldDatum {

        private final Class<?> declaringClass;
        private final String name;

        private static FieldDatum from(final Member member) {
            return new FieldDatum(member.getDeclaringClass(), member.getName());
        }

        private static FieldDatum from(final FieldAttributes fieldAttributes) {
            return new FieldDatum(fieldAttributes.getDeclaringClass(), fieldAttributes.getName());
        }

    }

}
@AllArgsConstructor(staticName = "of")
@Getter
final class FieldPatch<T> {

    private final Field field;
    private final BiFunction<? super Gson, ? super JsonElement, ? extends T> deserialize;

}

單元測試:

@AllArgsConstructor(access = AccessLevel.PACKAGE)
@EqualsAndHashCode
@ToString
final class ThirdPartyDTO {

    private final Instant foo;
    private final Instant bar;

}
public final class PostPatchFactoryTest {

    private static final Collection<FieldPatch<?>> fieldPatches;

    static {
        try {
            final Field thirdPartyDtoFooField = ThirdPartyDTO.class.getDeclaredField("foo");
            thirdPartyDtoFooField.setAccessible(true);
            final Field thirdPartyDtoBarField = ThirdPartyDTO.class.getDeclaredField("bar");
            thirdPartyDtoBarField.setAccessible(true);
            fieldPatches = ImmutableList.<FieldPatch<?>>builder()
                    .add(FieldPatch.of(thirdPartyDtoFooField, (gson, jsonElement) -> {
                        final String rawValue = jsonElement.getAsJsonObject()
                                .get("foo")
                                .getAsString();
                        return Instant.parse(rawValue);
                    }))
                    .add(FieldPatch.of(thirdPartyDtoBarField, (gson, jsonElement) -> {
                        final String rawValue = new StringBuilder(jsonElement.getAsJsonObject()
                                .get("bar")
                                .getAsString()
                        )
                                .reverse()
                                .toString();
                        return Instant.parse(rawValue);
                    }))
                    .build();
        } catch ( final NoSuchFieldException ex ) {
            throw new AssertionError(ex);
        }
    }

    private static final IPostPatchFactory unit = PostPatchFactory.create(fieldPatches);

    private static final Gson gson = new GsonBuilder()
            .disableInnerClassSerialization()
            .disableHtmlEscaping()
            .addDeserializationExclusionStrategy(unit.createExclusionStrategy())
            .registerTypeAdapterFactory(unit.createTypeAdapterFactory())
            .create();

    @Test
    public void test()
            throws IOException {
        final ThirdPartyDTO expected = new ThirdPartyDTO(Instant.ofEpochSecond(0), Instant.ofEpochSecond(0));
        try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(PostPatchFactoryTest.class.getResourceAsStream("input.json"))) ) {
            final ThirdPartyDTO actual = gson.fromJson(jsonReader, ThirdPartyDTO.class);
            Assertions.assertEquals(expected, actual);
        }
    }

}
{
    "foo": "1970-01-01T00:00:00Z",
    "bar": "Z00:00:00T10-10-0791"
}

(為簡單起見,該條只是一個反轉的字符串,使其對於bar格式模式變得模糊,但使測試更加健壯)

請注意,這種方法是通用的(並且可能適合Instant以外的任何其他類型),當反序列化包含特殊字段的類時,需要 JSON 樹在 memory 中緩沖(內置JsonSerializerJsonDeserializer做同樣的事情所以誰在乎?)失去對@SerializedName@JsonAdapter等的一些特殊支持。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM