Gson 在单个 JSON 对象中使用两种 List 类型进行序列化

Posted

技术标签:

【中文标题】Gson 在单个 JSON 对象中使用两种 List 类型进行序列化【英文标题】:Gson serialize with two List types in a single JSON object 【发布时间】:2017-09-10 12:52:57 【问题描述】:

我有两种类型的列表来使用 Gson 库绑定请求。我以某种方式尝试过,它可以正常工作。但我想知道我们是否有任何默认程序来序列化列表。

我在下面尝试并工作。但这不是实现的方式。寻找更好的解决方案。

class Request
    @Expose
    @SerializedName("ListC")
    private List<TypeOne> mList;

    public Request(List<TypeOne> listOne, List<TypeTwo> listTwo)
        this.mList = new ArrayList<>();
        for (int i = 0; i < listTwo.size(); i++) 
           // Adding the Typetwo values in TypeOne model class - Not best approach
           listOne.get(i).setAmount(listTwo.get(i).getAmount());
           listOne.get(i).setName(listTwo.get(i).getName());
           listOne.get(i).setType(listTwo.get(i).getType());
        
        this.mList.addAll(listOne);
    

    public String getJsonString()
         Gson gson = new GsonBuilder()
            .registerTypeAdapter(TypeOne.class, new Serializer())
            .registerTypeAdapter(Date.class, new DateSerializer())
            .excludeFieldsWithoutExposeAnnotation()
            .create();

    JsonObject in = new JsonObject();
    in.add("in", gson.toJsonTree(this));

    JsonObject obj = new JsonObject();
    obj.add("req", in);

    return obj.toString();
    

是否有人遇到过类似的问题并解决了这个问题,请指导我完成它。从很久以前开始尝试寻找更好的解决方案。

输出 JSON:


"list":[
    Obj
    "Type_1_object_1":"value",
    "Type_1_object_2":"value",
    "Type_1_object_3":"value",
    "Type_2_object_5":"value",
    "Type_2_object_6":"value"
    ,
    Obj
    "Type_1_object_1":"value",
    "Type_1_object_2":"value",
    "Type_1_object_3":"value", 
    "Type_2_object_5":"value", 
    "Type_2_object_6":"value"
    
]

【问题讨论】:

简单来说,你只是想压缩/合并输出 JSON 中的列表? 是的,使用 Gson 库将两种类型的列表添加到单个 jsonobject 中 【参考方案1】:

至少有两种从根本上不同的方法:

使用data transfer objects 让您使用对象映射控制结果 使用类型适配器(可能还有流式传输,但我不确定 Gson 是否适合您的情况)

让我们假设以下数据模型:

final class User 

    final String username;
    final String contact;

    User(final String username, final String contact) 
        this.username = username;
        this.contact = contact;
    


final class FooBar 

    final int foo;
    final int bar;

    FooBar(final int foo, final int bar) 
        this.foo = foo;
        this.bar = bar;
    


假设这两个应该被压缩/合并。

数据传输对象

结果 DTO 可能如下所示:

final class UserFooBarDto 

    final String username;
    final String contact;
    final Integer foo;
    final Integer bar;

    private UserFooBarDto(final String username, final String contact, final Integer foo, final Integer bar) 
        this.username = username;
        this.contact = contact;
        this.foo = foo;
        this.bar = bar;
    

    static UserFooBarDto userFooBarDto(final User user) 
        return new UserFooBarDto(user.username, user.contact, null, null);
    

    static UserFooBarDto userFooBarDto(final FooBar fooBar) 
        return new UserFooBarDto(null, null, fooBar.foo, fooBar.bar);
    

    static UserFooBarDto userFooBarDto(final User user, final FooBar fooBar) 
        return new UserFooBarDto(user.username, user.contact, fooBar.foo, fooBar.bar);
    


那么可以使用下面的代码来完成结果:

static void main(final String... args) 
    final List<User> users = ImmutableList.of(
            new User("john.doe", "john.doe@mail.com"),
            new User("alice.bob", "alice.and.bob@mail.com")
    );
    final List<FooBar> fooBars = ImmutableList.of(
            new FooBar(1, 2),
            new FooBar(3, 4),
            new FooBar(5, 6),
            new FooBar(7, 8)
    );
    final List<UserFooBarDto> zippedList = zip(users, fooBars, zipper);
    final String json = gson.toJson(zippedList);
    System.out.println(json);


private static final Gson gson = new Gson();

// It's a good idea to create such objects once, and use them everywhere where necessary not instantiating them over and over
private static final IZipper<User, FooBar, UserFooBarDto, List<UserFooBarDto>> zipper = new IZipper<User, FooBar, UserFooBarDto, List<UserFooBarDto>>() 
    @Override
    public List<UserFooBarDto> collectTo() 
        return new ArrayList<>();
    

    @Override
    public UserFooBarDto zip(final User user, final FooBar fooBar) 
        if ( user != null && fooBar != null ) 
            return userFooBarDto(user, fooBar);
         else if ( user != null ) 
            return userFooBarDto(user);
         else if ( fooBar != null ) 
            return userFooBarDto(fooBar);
         else 
            throw new AssertionError();
        
    
;

// A simple zipper interface that would tell where collect the zipped result to and how a zipped result element should be combined from
private interface IZipper<I1, I2, O, C extends Collection<O>> 

    C collectTo();

    O zip(I1 i1, I2 i2);



private static <I1, I2, O, C extends Collection<O>> C zip(
        final Iterable<? extends I1> list1,
        final Iterable<? extends I2> list2,
        final IZipper<? super I1, ? super I2, ? extends O, C> zipper
) 
    final C collection = zipper.collectTo();
    final Iterator<? extends I1> iterator1 = list1.iterator();
    final Iterator<? extends I2> iterator2 = list2.iterator();
    while ( iterator1.hasNext() || iterator2.hasNext() ) 
        final I1 i1 = iterator1.hasNext() ? iterator1.next() : null;
        final I2 i2 = iterator2.hasNext() ? iterator2.next() : null;
        final O o = zipper.zip(i1, i2);
        collection.add(o);
    
    return collection;

类型适配器

另一种更“动态”的方法是使用类型适配器,这可能有点难以实现,但可能更容易使用。

static void main(final String... args) 
    final List<User> users = ImmutableList.of(
            new User("john.doe", "john.doe@mail.com"),
            new User("alice.bob", "alice.and.bob@mail.com")
    );
    final List<FooBar> fooBars = ImmutableList.of(
            new FooBar(1, 2),
            new FooBar(3, 4),
            new FooBar(5, 6),
            new FooBar(7, 8)
    );
    final ZippedList<User, FooBar> zippedList = new ZippedList<>(users, fooBars);
    final String json = gson.toJson(zippedList, userAndFooBarZippedListType);
    System.out.println(json);


// TypeToken types are immutable types and can be safely assigned to static final fields
private static final Type userAndFooBarZippedListType = new TypeToken<ZippedList<User, FooBar>>() .getType();

// Gson instances are thread-safe and can be instantiated once too more saving instantiation time
private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(new ZippedListTypeAdapterFactory())
        .create();

// A special container class to let Gson pick a proper type adapter
private static final class ZippedList<T1, T2> 

    private final List<T1> list1;
    private final List<T2> list2;

    private ZippedList(final List<T1> list1, final List<T2> list2) 
        this.list1 = list1;
        this.list2 = list2;
    



private static final class ZippedListTypeAdapterFactory
        implements TypeAdapterFactory 

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) 
        // Not a class we must handle ourselves? Let Gson pick another best type adapter itself
        if ( !ZippedList.class.isAssignableFrom(typeToken.getRawType()) ) 
            return null;
        
        // Narrowing down the scope of @SuppressWarnings("unchecked") and making the type adapter to take care for nulls automatically
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new ZippedListTypeAdapter<>(gson).nullSafe();
        return typeAdapter;
    



private static final class ZippedListTypeAdapter<T1, T2>
        extends TypeAdapter<ZippedList<T1, T2>> 

    private final Gson gson;

    private ZippedListTypeAdapter(final Gson gson) 
        this.gson = gson;
    

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter out, final ZippedList<T1, T2> zippedList)
            throws IOException 
        // Write [ to the output
        out.beginArray();
        final Iterator<? extends T1> iterator1 = zippedList.list1.iterator();
        final Iterator<? extends T2> iterator2 = zippedList.list2.iterator();
        // Iterate over two sequences trying to merge their respective elements JSON representations
        while ( iterator1.hasNext() || iterator2.hasNext() ) 
            final T1 i1 = iterator1.hasNext() ? iterator1.next() : null;
            final T2 i2 = iterator2.hasNext() ? iterator2.next() : null;
            // This is not very efficient because it builds in-memory JSON trees thus consuming memory
            // It would be nice if it would be possible to decorate JsonWriter to control its beginObject and endObject
            // The latter control would help to suppress  and  at the top level, and delegate the real serialization to Gson with the decorated JsonWriter
            // But JsonWriter constructor requires a Writer, not at JsonWriter, and we do not have where to obtain a writer instance from
            // So we can just merge the trees...
            final JsonElement tree = mergeInto(gson.toJsonTree(i1), gson.toJsonTree(i2));
            gson.toJson(tree, out);
        
        // Write ] to the output
        out.endArray();
    

    @Override
    public ZippedList<T1, T2> read(final JsonReader in) 
        throw new UnsupportedOperationException();
    

    // JSON object types dispatching party...
    private static JsonElement mergeInto(final JsonElement e1, final JsonElement e2) 
        if ( e1.isJsonNull() ) 
            if ( e2.isJsonObject() ) 
                return mergeInto(e1.getAsJsonNull(), e2.getAsJsonObject());
             else 
                throw new AssertionError("TODO: " + e2.getClass());
            
         else if ( e1.isJsonObject() ) 
            if ( e2.isJsonObject() ) 
                return mergeInto(e1.getAsJsonObject(), e2.getAsJsonObject());
             else 
                throw new AssertionError("TODO: " + e2.getClass());
            
         else 
            throw new AssertionError("TODO: " + e1.getClass());
        
    

    // A bunch of specialized mergeInto overloads letting javac to pick the best one
    private static JsonObject mergeInto(@SuppressWarnings("unused") final JsonNull jsonNull1, final JsonObject jsonObject2) 
        return jsonObject2;
    

    private static JsonObject mergeInto(final JsonObject jsonObject1, final JsonObject jsonObject2) 
        for ( final Entry<String, JsonElement> e : jsonObject2.entrySet() ) 
            jsonObject1.add(e.getKey(), e.getValue());
        
        return jsonObject1;
    


两个示例都生成以下 JSON(美化):

[
    
        "username": "john.doe",
        "contact": "john.doe@mail.com",
        "foo": 1,
        "bar": 2
    ,
    
        "username": "alice.bob",
        "contact": "alice.and.bob@mail.com",
        "foo": 3,
        "bar": 4
    ,
    
        "foo": 5,
        "bar": 6
    ,
    
        "foo": 7,
        "bar": 8
    
]

【讨论】:

我无法测试 TypeAdapter 代码。私有静态最终类型 userAndFooBarZippedListType = TypeToken.getParameterized(ZippedList.class, User.class, FooBar.class).getType();创建错误 @AbishR 可能会在最近的 Gson 版本中引入。我正在使用 2.8.0。但是,我刚刚编辑了答案。 谢谢@Lyubomyr Shaydariv,它成功了。但是 json 应该构建在单个对象中。它正在创建两个单独的 json 对象。它应该从这两个列表中创建一个对象。 @AbishR 好吧。如有必要,您可以重新配置它...您想要的 JSON 输出在结构上与上面的结果不匹配吗? 重新配置 JSON?我们不能。您的代码将输出显示为 [],[]

以上是关于Gson 在单个 JSON 对象中使用两种 List 类型进行序列化的主要内容,如果未能解决你的问题,请参考以下文章

Gson使用

Gson转换json数据为对象

Android Gson 使用详解

单个 ListView 中的不同对象

在 Java 中使用 Gson 合并/扩展 JSON 对象

Gson、FastJson、Jackson、json-lib对比总结