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 类型进行序列化的主要内容,如果未能解决你的问题,请参考以下文章