Gson 在单个 JSON 对象中使用两种列表类型进行序列化
Gson serialize with two List types in a single JSON object
我有两种类型的列表来使用 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"
}
]
}
至少有两种方法在根本上是不同的:
- 使用 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;
}
}
假设这两个应该是zipped/merged。
数据传输对象
结果 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;
}
类型适配器
另一种更 "dynamic" 的方法是使用类型适配器,这种适配器可能难以实现,但可能更易于使用。
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
}
]
我有两种类型的列表来使用 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"
}
]
}
至少有两种方法在根本上是不同的:
- 使用 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;
}
}
假设这两个应该是zipped/merged。
数据传输对象
结果 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;
}
类型适配器
另一种更 "dynamic" 的方法是使用类型适配器,这种适配器可能难以实现,但可能更易于使用。
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
}
]