TL;DR:Gson 不能使用 Pair,因为没有办法实例化它(至少 Gson 知道:除非你告诉 Gson 使用 Pair.of)。
很抱歉,您的解决方案存在一些问题。
- 不要在可以使用
List的声明站点绑定ArrayList。
- 从
ArrayList 扩展Parameters 是一个相当糟糕的选择(ArrayList 有那么特别吗?如果你将来想要LinkedList 怎么办?如果你需要一个Set 怎么办?哪个设置?) - - 更喜欢聚合和封装集合。
-
Pair 可以替换为Map.Entry -- 尽可能使用最通用的类型(类似于上一项)。
- 令人惊讶的是,对此类对/条目使用
List可能比使用Map 更好(如果需要以多映射方式存储多个键怎么办?)
- 不幸的是,Gson 不会让您轻松序列化/反序列化
Object,因为 无法从 JSON 中重构原始对象(Gson 应该如何知道要反序列化的类,而有数万个类?哪一个更可取?为什么要为启发式方法或将类型标签直接烘焙到 JSON 中以让 Gson 成为原始对象类(如果可能,包括参数化)?)——在 @ 上查看更多关于类似问题的 cmets 987654321@.
使其与 Gson 一起工作的一种可能方法是迁移到字符串-字符串对(以便 Gson 知道 如何 为键和值进行 JSON 往返)并编写一对- 感知类型适配器工厂。
final class PairTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new PairTypeAdapterFactory();
private PairTypeAdapterFactory() {
}
static TypeAdapterFactory getInstance() {
return instance;
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Pair.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final ParameterizedType parameterizedType = (ParameterizedType) typeToken.getType();
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
final TypeAdapter<Pair<Object, Object>> pairTypeAdapter = resolveTypeAdapter(gson, actualTypeArguments[0], actualTypeArguments[1]);
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) pairTypeAdapter;
return typeAdapter;
}
private <L, R> TypeAdapter<Pair<L, R>> resolveTypeAdapter(final Gson gson, final Type leftType, final Type rightType) {
@SuppressWarnings("unchecked")
final TypeAdapter<L> leftTypeAdapter = (TypeAdapter<L>) gson.getDelegateAdapter(this, TypeToken.get(leftType));
@SuppressWarnings("unchecked")
final TypeAdapter<R> rightTypeAdapter = (TypeAdapter<R>) gson.getDelegateAdapter(this, TypeToken.get(rightType));
return new TypeAdapter<Pair<L, R>>() {
@Override
public void write(final JsonWriter out, final Pair<L, R> value)
throws IOException {
out.beginArray();
leftTypeAdapter.write(out, value.getLeft());
rightTypeAdapter.write(out, value.getRight());
out.endArray();
}
@Override
public Pair<L, R> read(final JsonReader in)
throws IOException {
in.beginArray();
final L left = leftTypeAdapter.read(in);
final R right = rightTypeAdapter.read(in);
in.endArray();
return Pair.of(left, right);
}
}
.nullSafe();
}
}
上面的代码非常简单,它还使用数组来打包对,因为您可能想要存储重复的键并节省一些空间(此外,二元素数组很可能是对的良好载体)。
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class Event {
String type;
List<Pair<String, String>> params;
}
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class JsonFile {
List<Event> events;
}
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.registerTypeAdapterFactory(PairTypeAdapterFactory.getInstance())
.create();
@Test
public void test() {
final JsonFile before = new JsonFile();
final List<Pair<String, String>> params = new ArrayList<>();
params.add(Pair.of("xyzzy", "is a magic name"));
params.add(Pair.of("foo", "bar"));
final Event event = new Event("lispy event", params);
before.events = new ArrayList<>();
before.events.add(event);
final String json = gson.toJson(before, JsonFile.class);
final JsonFile after = gson.fromJson(json, JsonFile.class);
Assertions.assertEquals(before, after);
}
中间JSON如下:
{
"events": [
{
"type": "lispy event",
"params": [
[
"xyzzy",
"is a magic name"
],
[
"foo",
"bar"
]
]
}
]
}
另一个使用 Gson 的(有点疯狂的)想法是为Object 实现一个启发式类型适配器,并使用自定义类型使用注释进行注释,例如 @Heuristics(这些类型的注释随 Java 8 一起提供)。这将允许您像这样参数化对:Pair<String, @Heuristics Object>,但据我所知,Gson 无法在类型标记中携带类型使用注释,因此类型适配器工厂无法获取它们并应用自定义类型适配器(注意:为Object 实现这样的类型适配器,即使是那些没有注释的,也可能会导致不需要的全局效果。
根据您对此的新信息,我可能是这样实现的(没有太多评论,但如果有更多其他要求,可以重新实现):
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class JsonFile {
List<Event<?>> events;
}
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
final class Event<T> {
String type;
Map<String, T> params;
}
final class EventTypeAdapterFactory
implements TypeAdapterFactory {
private final Map<String, Class<?>> typeMap;
private EventTypeAdapterFactory(final Map<String, Class<?>> typeMap) {
this.typeMap = typeMap;
}
static TypeAdapterFactory getInstance(final Map<String, Class<?>> typeMap) {
return new EventTypeAdapterFactory(typeMap);
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Event.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final Map<String, TypeAdapter<?>> typeAdapterMap = typeMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> gson.getDelegateAdapter(this, TypeToken.getParameterized(Map.class, String.class, e.getValue()))));
final TypeAdapter<Event<?>> typeAdapter = new TypeAdapter<Event<?>>() {
@Override
public void write(final JsonWriter out, final Event<?> value)
throws IOException {
out.beginObject();
out.name("type");
out.value(value.type);
out.name("params");
@Nullable
@SuppressWarnings("unchecked")
final TypeAdapter<Object> paramsTypeAdapter = (TypeAdapter<Object>) typeAdapterMap.get(value.type);
if ( paramsTypeAdapter == null ) {
throw new IllegalStateException(value.type + " is not mapped to a specific type");
}
paramsTypeAdapter.write(out, value.params);
out.endObject();
}
@Override
public Event<?> read(final JsonReader in) {
final JsonObject root = Streams.parse(in)
.getAsJsonObject();
final String type = root.getAsJsonPrimitive("type")
.getAsString();
@Nullable
@SuppressWarnings("unchecked")
final TypeAdapter<Object> paramsTypeAdapter = (TypeAdapter<Object>) typeAdapterMap.get(type);
if ( paramsTypeAdapter == null ) {
throw new IllegalStateException(type + " is not mapped to a specific type");
}
final JsonObject rawParams = root.getAsJsonObject("params");
@SuppressWarnings("unchecked")
final Map<String, ?> params = (Map<String, ?>) paramsTypeAdapter.fromJsonTree(rawParams);
return new Event<>(type, params);
}
}
.nullSafe();
@SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
return castTypeAdapter;
}
}
public final class EventTypeAdapterFactoryTest {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.registerTypeAdapterFactory(EventTypeAdapterFactory.getInstance(ImmutableMap.of("lispy event", String.class)))
.create();
@Test
public void test() {
final JsonFile before = new JsonFile();
final Map<String, Object> params = new LinkedHashMap<>();
params.put("xyzzy", "is a magic name");
params.put("foo", "bar");
final Event<Object> event = new Event<>("lispy event", params);
before.events = new ArrayList<>();
before.events.add(event);
final String json = gson.toJson(before, JsonFile.class);
System.out.println(json);
final JsonFile after = gson.fromJson(json, JsonFile.class);
Assertions.assertEquals(before, after);
}
}
{
"events": [
{
"type": "lispy event",
"params": {
"xyzzy": "is a magic name",
"foo": "bar"
}
}
]
}