基于GSON中字段名称的同类型序列化,不带注释 [英] Same type serialization based on field name in GSON without annotations

查看:77
本文介绍了基于GSON中字段名称的同类型序列化,不带注释的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出一个我无法修改的课程

  class ThirdPartyDTO{即时foo;即时酒吧;//还有更多字段.} 

我有该类的JSON表示形式,它使用两种不同的模式来表示foo和bar.

如果字段名称是foo,请使用此模式;如果字段名称是bar,请使用其他模式.

如何使用gson而不在每个字段名称上添加(因为我不能)注释?

谢谢.

解决方案

因此,正如我在上面的评论中提到的那样,Gson类型适配器无法访问其序列化或反序列化的对象的完整上下文.例如,用于单个类型(层次结构)的类型适配器并不真正知道它可能应用于哪个字段(这是帖子中的问题).为了将不同类型的适配器应用于不同的字段,可以使用 JsonSerializer JsonDeserializer (因此,必须手动处理每个字段,这是一项繁琐的工作).另一个不好的地方是,应该处理此类DTO的 ReflectiveTypeAdapterFactory 不能直接扩展,而只能通过受限制的 GsonBuilder 接口进行扩展.

但是,可以实现使用以下算法的解决方法:

  • 创建一种排除策略,该策略始终在反序列化时跳过特殊字段(这只会影响 ReflectiveTypeAdapterFactory );
  • 创建类型适配器工厂,该工厂为此类特殊字段创建类型适配器;
  • 一旦Gson反序列化了包装器对象,应该跳过包装器对象中的特殊字段,但是将其设置为 null (对于原语,则为其他默认值),反序列化器后类型适配器会询问注入策略来反序列化以前被排除策略跳过的每个特殊字段,因此 ReflectiveTypeAdapterFactory .

那是窍门.

 接口IPostPatchFactory {@NonnullTypeAdapterFactory createTypeAdapterFactory();@NonnullExclusionStrategy createExclusionStrategy();} 

  @AllArgsConstructor(access = AccessLevel.PRIVATE)最后一课PostPatchFactory实现IPostPatchFactory {私人最终谓词< ;?超级FieldDatum>isFieldPostPatched;私人最终谓词< ;?超级类<?>isClassPostPatched;私有最终Iterable< FieldPatch<?>fieldPatches;静态IPostPatchFactory create(final Collection< FieldPatch<?> fieldPatches){最终Collection< FieldPatch<?>fieldPatchesCopy = new ArrayList(fieldPatches);最终Collection< Field>postPatchedFields = fieldPatches.stream().map(FieldPatch :: getField).collect(Collectors.toList());最终Collection< FieldDatum>postPatchedFieldAttributes = postPatchedFields.stream().map(FieldDatum :: from).collect(Collectors.toList());最终收藏 fieldDatum.declaringClass).collect(Collectors.toList());返回新的PostPatchFactory(postPatchedFieldAttributes :: contains,isClassPostPatched :: contains,fieldPatchesCopy);}@Nonnull@Override公共TypeAdapterFactory createTypeAdapterFactory(){返回新的PostPatchTypeAdapterFactory(isClassPostPatched,fieldPatches);}@Nonnull@Override公共ExclusionStrategy createExclusionStrategy(){返回新的PostPatchExclusionStrategy(isFieldPostPatched);}@AllArgsConstructor(访问权限= AccessLevel.PRIVATE)私有静态最终类PostPatchTypeAdapterFactory实现TypeAdapterFactory {私人最终谓词< ;?超级类<?>isClassPostPatched;私有最终Iterable< FieldPatch<?>fieldPatches;@Override@Nullable公共< T>TypeAdapter< T>create(final Gson gson,final TypeToken< T> typeToken){最终班< ;?超级TrawType = typeToken.getRawType();如果(!isClassPostPatched.test(rawType)){返回null;}返回新的PostPatchTypeAdapter(gson,gson.getDelegateAdapter(this,typeToken),fieldPatches).nu​​llSafe();}@AllArgsConstructor(访问权限= AccessLevel.PRIVATE)私有静态最终类PostPatchTypeAdapter< T>扩展TypeAdapter< T>{私人决赛Gson Gson;私有最终TypeAdapter< T>proxyTypeAdapter;私有最终Iterable< FieldPatch<?>fieldPatches;@Override公共无效写入(最终JsonWriter输出,最终T值){抛出新的UnsupportedOperationException("TODO");}@Override公共T读取(最终JsonReader输入){最后的JsonElement bufferedJsonElement = JsonParser.parseReader(in);最终T值=委托类型适配器.fromJsonTree(bufferedJsonElement);for(final FieldPatch<?> fieldPatch:fieldPatches){最终Field字段= fieldPatch.getField();最终BiFunction< ;?超级格森?超级JsonElement,?>deserialize = fieldPatch.getDeserialize();最终对象fieldValue = deserialize.apply(gson,bufferedJsonElement);尝试 {field.set(value,fieldValue);} catch(final IllegalAccessException ex){抛出新的RuntimeException(ex);}}返回值;}}}私有静态最终类PostPatchExclusionStrategy实施ExclusionStrategy {私人最终谓词< ;?超级FieldDatum>isFieldPostPatched;私人PostPatchExclusionStrategy(final Predicate< ;? super FieldDatum> isFieldPostPatched){this.isFieldPostPatched = isFieldPostPatched;}@Overridepublic boolean shouldSkipField(final FieldAttributes fieldAttributes){返回isFieldPostPatched.test(FieldDatum.from(fieldAttributes));}@Overridepublic boolean shouldSkipClass(final Class<?> clazz){返回false;}}@AllArgsConstructor(访问权限= AccessLevel.PRIVATE)@EqualsAndHashCode私有静态最终类别FieldDatum {私人最终班级<?>声明类;私有的最终字符串名称;来自(最终成员)的私有静态FieldDatum {返回新的FieldDatum(member.getDeclaringClass(),member.getName());}私人静态FieldDatum from(final FieldAttributes fieldAttributes){返回新的FieldDatum(fieldAttributes.getDeclaringClass(),fieldAttributes.getName());}}} 

  @AllArgsConstructor(staticName ="of")@盖特最终类FieldPatch< T>{私人最终Field字段;私有最终BiFunction< ;?超级格森,?超级JsonElement ,?扩展T>反序列化} 

单元测试:

  @AllArgsConstructor(access = AccessLevel.PACKAGE)@EqualsAndHashCode@ToString最终课程ThirdPartyDTO {私人最终Instant foo;私人最终即时酒吧;} 

 公共最终类PostPatchFactoryTest {私有静态最终Collection< FieldPatch<?>fieldPatches;静止的 {尝试 {最终字段thirdPartyDtoFooField = ThirdPartyDTO.class.getDeclaredField("foo");thirdPartyDtoFooField.setAccessible(true);final Field thirdPartyDtoBarField = ThirdPartyDTO.class.getDeclaredField("bar");thirdPartyDtoBarField.setAccessible(true);< FieldPatch<?> builder()..add(FieldPatch.of(thirdPartyDtoFooField,(gson,jsonElement)-> {最终字符串rawValue = jsonElement.getAsJsonObject().get("foo").getAsString();返回Instant.parse(rawValue);})).add(FieldPatch.of(thirdPartyDtoBarField,(gson,jsonElement)-> {最终的字符串rawValue =新的StringBuilder(jsonElement.getAsJsonObject().get("bar").getAsString()).撤销().toString();返回Instant.parse(rawValue);})).建造();} catch(final NoSuchFieldException ex){抛出新的AssertionError(ex);}}私有静态最终IPostPatchFactory单元= PostPatchFactory.create(fieldPatches);私有静态最终Gson gson =新的GsonBuilder().disableInnerClassSerialization().disableHtmlEscaping().addDeserializationExclusionStrategy(unit.createExclusionStrategy()).registerTypeAdapterFactory(unit.createTypeAdapterFactory()).创造();@测试公共无效测试()引发IOException {最终的ThirdPartyDTO预期=新的ThirdPartyDTO(Instant.ofEpochSecond(0),Instant.ofEpochSecond(0));试试(final JsonReader jsonReader = new JsonReader(new InputStreamReader(PostPatchFactoryTest.class.getResourceAsStream("input.json"))))){最后的ThirdPartyDTO实际= gson.fromJson(jsonReader,ThirdPartyDTO.class);Assertions.assertEquals(预期的,实际的);}}} 

 <代码> {" foo":" 1970-01-01T00:00:00Z" ;,"bar":"Z00:00:00T10-10-0791"} 

(为简单起见, bar 只是一个反向字符串,使它对于Java格式模式不明显,但使测试更加健壮)

请注意,此方法是泛型(并且可能适合 Instant 以外的任何其他类型),在反序列化包含特殊字段的类时需要将JSON树缓存在内存中(内置的 JsonSerializer JsonDeserializer 一样,所以谁在乎呢?),并失去了对 @SerializedName @的一些特殊支持.JsonAdapter 等.

Given a class that I cannot modifiy

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.

If the field name is foo, use this pattern, if the field name is bar, use the other pattern.

How can I do this with gson without adding (because I can't) annotations on each field name?

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. 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). 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.

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);
  • 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.

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)

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.

这篇关于基于GSON中字段名称的同类型序列化,不带注释的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆