简体   繁体   English

无法使用Gson使用可迭代字段正确序列化POJO

[英]Can't properly serialize POJO with Iterable fields using Gson

Here is my POJO I am trying to serialize: 这是我要序列化的POJO:

public class Bar {
    private final Foo foo;

    private final Iterable<String> list;

    private final Iterable<Map<String, String>> listOfMaps;
}

Here is how I'm calling it 这是我的称呼方式

Bar bar = new Bar();
Foo foo = new Foo();
foo.field1 = "val1";
foo.field2 = "val2";
bar.foo = foo;
bar.list = ImmutableList.<String>of("fooList");
bar.listOfMaps = ImmutableList.<Map<String,String>>of(
                    ImmutableMap.<String,String>of("key", "val")
                );
new Gson().toJson(bar);

Here is the result 这是结果

{"foo":{"field1":"val1","field2":"val2"},"list":{},"listOfMaps":{}}

As you can see, the POJO serializes fine, but the iterable (instance of guava collections) doesn't serialize to JSON properly. 如您所见,POJO可以很好地序列化,但是可迭代(番石榴集合的实例)不能正确地序列化为JSON。 When I serialize the fields on their own, they show up fine, but it won't properly serialize when they are fields of Bar 当我自行序列化字段时,它们显示很好,但是当它们是Bar字段时,它将无法正确序列化

Example: 例:

new Gson().toJson(bar.list);

["fooList"]

It looks like an old issue for Gson and this is how it was initially designed. 对于Gson来说,这似乎是一个老问题,这就是最初设计的方式。 I have some assumptions why it might be designed like that, but I think my assumptions are weak ( Iterable is just too base type to get a particular implementation by default?; it may intersect with Collection and List ?; what if Iterable returns an infinite iterator?). 我有一些假设,为什么可能会这样设计,但是我认为我的假设很弱( Iterable太基类型,默认情况下无法获得特定实现?;它可能与CollectionList相交?;如果Iterable返回无限迭代器?)。 You can track down the Iterable support issue through this pull request: https://github.com/google/gson/pull/854 . 您可以通过以下拉取请求跟踪Iterable支持问题: https : //github.com/google/gson/pull/854

First of all, why Gson behaves differently for both cases you mentioned ( .toJson(bar) and .toJson(bar.list) ). 首先,为什么Gson在您提到的两种情况下都有不同的行为( .toJson(bar).toJson(bar.list) )。 For the first case, Gson scans your bar object and it retrieves type information directly from the fields using reflection, and this is where it gets Iterable . 对于第一种情况,Gson扫描您的bar对象,并使用反射直接从字段中检索类型信息,这就是它获取Iterable For the second case, Gson does not have declaration type information therefore it just takes the actual class (not type!) using .getClass() losing type parameterization (strings fit this test cast just perfect), Collection in this case that has Gson support out of box. 对于第二种情况,Gson没有声明类型信息,因此仅使用.getClass().getClass()实际的类(而不是类型! .getClass()丢失类型参数化(字符串适合此测试.getClass()是完美的),在这种情况下, Collection具有Gson支持开箱即用。 Note that the latter can also be reproduced with gson.toJson(..., new TypeToken<Iterable<String>>() {}.getType(), System.out) suggesting the given object type (not class!) explicitly. 请注意,后者也可以通过gson.toJson(..., new TypeToken<Iterable<String>>() {}.getType(), System.out)进行复制, gson.toJson(..., new TypeToken<Iterable<String>>() {}.getType(), System.out)明确建议给定的对象类型 (而不是类!)。

However, you can add Iterable support yourself, but I'm not sure if the implementation below requires more work: 但是,您可以自己添加Iterable支持,但是我不确定以下实施是否需要更多工作:

final class IterableMyTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory iterableTypeAdapterFactory = new IterableMyTypeAdapterFactory();

    private IterableMyTypeAdapterFactory() {
    }

    // It's an effective singleton but we do not reveal it
    static TypeAdapterFactory getIterableMyTypeAdapterFactory() {
        return iterableTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Must be an iterable, subclasses should be picked up by built-in type adapters
        if ( !Iterable.class.isAssignableFrom(typeToken.getRawType()) || Collection.class.isAssignableFrom(typeToken.getRawType()) ) {
            // Tell Gson to pick up on its own
            return null;
        }
        // Extract the element type. If it's raw, we assume java.lang.Object is in the play
        final Type elementType = getTypeParameter0(typeToken.getType());
        // Get the element type adapter
        final TypeAdapter<?> elementTypeAdapter = gson.getDelegateAdapter(this, TypeToken.get(elementType));
        // And get rid of wildcards
        @SuppressWarnings("unchecked")
        final TypeAdapter<Object> castElementTypeAdapter = (TypeAdapter<Object>) elementTypeAdapter;
        // Instantiating a new iterable type adapter
        final TypeAdapter<Iterable<Object>> iterableTypeAdapter = IterableTypeAdapter.get(castElementTypeAdapter);
        // Cast it cheating javac
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) iterableTypeAdapter;
        return castTypeAdapter;
    }

    private static Type getTypeParameter0(final Type type) {
        if ( !(type instanceof ParameterizedType) ) {
            return Object.class;
        }
        final ParameterizedType parameterizedType = (ParameterizedType) type;
        return parameterizedType.getActualTypeArguments()[0];
    }

    private static final class IterableTypeAdapter<E>
            extends TypeAdapter<Iterable<E>> {

        private final TypeAdapter<E> elementTypeAdapter;

        private IterableTypeAdapter(final TypeAdapter<E> elementTypeAdapter) {
            this.elementTypeAdapter = elementTypeAdapter;
        }

        private static <E> TypeAdapter<Iterable<E>> get(final TypeAdapter<E> elementTypeAdapter) {
            return new IterableTypeAdapter<>(elementTypeAdapter)
                    .nullSafe(); // We don't need to handle nulls ourselves anymore
        }

        @Override
        @SuppressWarnings("resource")
        public void write(final JsonWriter jsonWriter, final Iterable<E> elements)
                throws IOException {
            // Emit [
            jsonWriter.beginArray();
            for ( final E e : elements ) {
                // Write each element to the downstream writer
                elementTypeAdapter.write(jsonWriter, e);
            }
            // Emit ]
            jsonWriter.endArray();
        }

        @Override
        @SuppressWarnings("resource")
        public Iterable<E> read(final JsonReader jsonReader)
                throws IOException {
            jsonReader.beginArray(); // Expect [
            final Collection<E> elements = new ArrayList<>(); // This is probably why there is Iterable support by default
            while ( jsonReader.hasNext() ) {
                final E e = elementTypeAdapter.read(jsonReader); // Read each element
                elements.add(e);
            }
            jsonReader.endArray(); // Expect ]
            return elements;
        }

    }

}
private static final class Pack {

    Iterable<String> iterable;
    Collection<String> collection;
    List<String> list;
    Map<String, String> map;

}

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getIterableMyTypeAdapterFactory())
        .create();

public static void main(final String... args) {
    final List<String> fooBar = ImmutableList.of("foo", "bar");
    final Pack pack = new Pack();
    pack.iterable = fooBar;
    pack.collection = fooBar;
    pack.list = fooBar;
    pack.map = ImmutableMap.of("foo", "bar");
    gson.toJson(pack, System.out);
}

Output before: 输出之前:

{"iterable":{},"collection":["foo","bar"],"list":["foo","bar"],"map":{"foo":"bar"}}

Output after: 输出后:

{"iterable":["foo","bar"],"collection":["foo","bar"],"list":["foo","bar"],"map":{"foo":"bar"}}

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

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