简体   繁体   English

基于GSON中字段名的同类型序列化不带注解

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

Given a class that I cannot modifiy给定一个我无法修改的 class

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

I have a JSON representation of the class that uses two diferent patterns to represent foo and bar.我有 class 的 JSON 表示,它使用两种不同的模式来表示 foo 和 bar。

If the field name is foo, use this pattern, if the field name is bar, use the other pattern.如果字段名称为 foo,则使用此模式,如果字段名称为 bar,则使用其他模式。

How can I do this with gson without adding (because I can't) annotations on each field name?如何使用 gson 做到这一点而不在每个字段名称上添加(因为我不能)注释?

Thanks.谢谢。

So, as I mentioned in the comments above, Gson type adapters do not have access to the full context of the objects they serialize or deserialize.因此,正如我在上面的评论中提到的,Gson 类型的适配器无法访问它们序列化或反序列化的对象的完整上下文。 For example, a type adapter for a single type (hierarchy) does not really know what field it may be applied to (and this is the problem in the post).例如,单个类型(层次结构)的类型适配器并不真正知道它可能应用于哪个字段(这就是帖子中的问题)。 In order to apply different type adapters for different fields, JsonSerializer and JsonDeserializer can be used (therefore every field must be processed manually that is a tedious job).为了对不同的字段应用不同的类型适配器,可以使用JsonSerializerJsonDeserializer (因此每个字段都必须手动处理,这是一项繁琐的工作)。 Another bad thing here is that the ReflectiveTypeAdapterFactory that is supposed to process DTOs like that is not extensible directly but can only be extended via the GsonBuilder interface that is also limited.这里的另一个坏事是,应该像这样处理 DTO 的ReflectiveTypeAdapterFactory不能直接扩展,而只能通过同样有限的GsonBuilder接口进行扩展。

However, it is possible to implement a workaround that uses the following algorithm:但是,可以实现使用以下算法的解决方法:

  • create an exclusion strategy that always skips special fields on deserialization (this affects the ReflectiveTypeAdapterFactory only);创建一个排除策略,总是在反序列化时跳过特殊字段(这仅影响ReflectiveTypeAdapterFactory );
  • create a type adapter factory that creates type adapters for such special fields;创建一个类型适配器工厂,为这些特殊字段创建类型适配器;
  • 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 skipped by the exclusion strategy hence 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

That's the trick.这就是诀窍。

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;

}

The unit test:单元测试:

@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"
}

(for simplicity, the bar is simply a reversed string to make it obscure for Java format pattern, but make the test more robust) (为简单起见,该条只是一个反转的字符串,使其对于bar格式模式变得模糊,但使测试更加健壮)

Note that this approach is generic (and may fit any other type other than Instant ), requires JSON trees to be buffered in memory when deserializing classes that contain special fields (built-in JsonSerializer and JsonDeserializer do the same so who cares?), and lose some special support for @SerializedName , @JsonAdapter , etc.请注意,这种方法是通用的(并且可能适合Instant以外的任何其他类型),当反序列化包含特殊字段的类时,需要 JSON 树在 memory 中缓冲(内置JsonSerializerJsonDeserializer做同样的事情所以谁在乎?)失去对@SerializedName@JsonAdapter等的一些特殊支持。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM