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
    }
]