Gson:要列出的索引对象
Gson: indexed object to list
我有一个来自外部 API 的 json 对象,它具有以下形式的对象“列表”:
{
"width": 10,
"height": 20
"pointData": {
"0": {
"x": 7,
"y": 5,
...
},
"1": {
"x": 4,
"y": 20,
...
},
"2": {
"x": 1,
"y": 3,
...
},
...
}
}
我的反序列化器和 POJO 看起来像这样:
class KeyedListDeserializer<T> implements JsonDeserializer<List<T>> {
@Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
List<T> things = new ArrayList<>();
int key = 0;
while (jsonObject.has(String.valueOf(key))) {
JsonElement element = jsonObject.get(String.valueOf(key));
T value = context.deserialize(element, new TypeToken<T>(){}.getType());
things.add(value);
key += 1;
}
return things;
}
}
class PointDataKeyedList extends KeyedListDeserializer<PointData> {}
class Canvas {
int width;
int height;
@JsonAdapter(PointDataKeyedList.class)
List<PointData> pointData;
...
}
class PointData {
int x;
int y;
...
}
由于 pointData
对象的所有键都只是索引,我想我可以将其反序列化为列表而不是映射。当运行这个时候,Gson反序列化成功,但是当我尝试访问点数据时,我得到一个:
Exception in thread "main" java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class PointData (com.google.gson.internal.LinkedTreeMap and PointData are in unnamed module of loader 'app')
我不确定这里出了什么问题。我读过一些关于类型擦除的内容,这是我在搜索这个问题时经常听到的,但我并不是很了解它。
我担心你的 POJO 类 需要像
class Canvas {
int width;
int height;
Map<String, PointData> pointData;
...
}
class PointData {
int x;
int y;
...
}
你可以摆脱 PointDataKeyedList 和 KeyedListDeserializer
public static void main(String[] args) {
String json = "{\n" +
" \"width\": 10,\n" +
" \"height\": 20,\n" +
" \"pointData\": {\n" +
" \"0\": {\n" +
" \"x\": 7,\n" +
" \"y\": 5\n" +
" },\n" +
" \"1\": {\n" +
" \"x\": 4,\n" +
" \"y\": 20\n" +
" },\n" +
" \"2\": {\n" +
" \"x\": 1,\n" +
" \"y\": 3\n" +
" }\n" +
" }\n" +
"}";
System.out.println(json);
Gson gson = new Gson();
Canvas canvas = gson.fromJson(json, Canvas.class);
System.out.println(canvas);
}
在 Gson 中非常简单。
public final class MapToListTypeAdapterFactory
implements TypeAdapterFactory {
private MapToListTypeAdapterFactory() {
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final Type elementType = typeToken.getType() instanceof ParameterizedType
? ((ParameterizedType) typeToken.getType()).getActualTypeArguments()[0]
: Object.class;
final TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
final TypeAdapter<List<Object>> listTypeAdapter = new TypeAdapter<List<Object>>() {
@Override
public void write(final JsonWriter out, final List<Object> value) {
throw new UnsupportedOperationException();
}
@Override
public List<Object> read(final JsonReader in)
throws IOException {
in.beginObject();
final ArrayList<Object> list = new ArrayList<>();
while ( in.hasNext() ) {
final int index = Integer.parseInt(in.nextName());
final Object element = elementTypeAdapter.read(in);
ensureSize(list, index + 1);
list.set(index, element);
}
in.endObject();
return list;
}
};
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) listTypeAdapter
.nullSafe();
return typeAdapter;
}
//
private static void ensureSize(final ArrayList<?> list, final int size) {
list.ensureCapacity(size);
while ( list.size() < size ) {
list.add(null);
}
}
}
上面的类型适配器工厂做了以下事情:
- 检查目标类型是否为
List
(它不适用于链表,但可以简化);
- 提取目标列表的类型参数,从而提取其元素类型并解析相应的类型适配器;
- 用地图读取适配器替换原始类型适配器,该适配器读取地图键并将它们假定为新列表索引(并在必要时扩大结果列表)并使用原始元素类型适配器读取每个地图值。请注意,列表可能具有稀疏索引的假设是易受攻击的,我只是出于演示目的将其放置(例如,输入 JSON 声明了一个单元素映射,其唯一索引“999999”显着扩大了列表) , 因此您可以仅使用
add(element)
方法忽略 index
值。
只需将 Canvas
class 中的 pointData
字段注释为 @JsonAdapter(MapToListTypeAdapterFactory.class)
即可。
我有一个来自外部 API 的 json 对象,它具有以下形式的对象“列表”:
{
"width": 10,
"height": 20
"pointData": {
"0": {
"x": 7,
"y": 5,
...
},
"1": {
"x": 4,
"y": 20,
...
},
"2": {
"x": 1,
"y": 3,
...
},
...
}
}
我的反序列化器和 POJO 看起来像这样:
class KeyedListDeserializer<T> implements JsonDeserializer<List<T>> {
@Override
public List<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
List<T> things = new ArrayList<>();
int key = 0;
while (jsonObject.has(String.valueOf(key))) {
JsonElement element = jsonObject.get(String.valueOf(key));
T value = context.deserialize(element, new TypeToken<T>(){}.getType());
things.add(value);
key += 1;
}
return things;
}
}
class PointDataKeyedList extends KeyedListDeserializer<PointData> {}
class Canvas {
int width;
int height;
@JsonAdapter(PointDataKeyedList.class)
List<PointData> pointData;
...
}
class PointData {
int x;
int y;
...
}
由于 pointData
对象的所有键都只是索引,我想我可以将其反序列化为列表而不是映射。当运行这个时候,Gson反序列化成功,但是当我尝试访问点数据时,我得到一个:
Exception in thread "main" java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class PointData (com.google.gson.internal.LinkedTreeMap and PointData are in unnamed module of loader 'app')
我不确定这里出了什么问题。我读过一些关于类型擦除的内容,这是我在搜索这个问题时经常听到的,但我并不是很了解它。
我担心你的 POJO 类 需要像
class Canvas {
int width;
int height;
Map<String, PointData> pointData;
...
}
class PointData {
int x;
int y;
...
}
你可以摆脱 PointDataKeyedList 和 KeyedListDeserializer
public static void main(String[] args) {
String json = "{\n" +
" \"width\": 10,\n" +
" \"height\": 20,\n" +
" \"pointData\": {\n" +
" \"0\": {\n" +
" \"x\": 7,\n" +
" \"y\": 5\n" +
" },\n" +
" \"1\": {\n" +
" \"x\": 4,\n" +
" \"y\": 20\n" +
" },\n" +
" \"2\": {\n" +
" \"x\": 1,\n" +
" \"y\": 3\n" +
" }\n" +
" }\n" +
"}";
System.out.println(json);
Gson gson = new Gson();
Canvas canvas = gson.fromJson(json, Canvas.class);
System.out.println(canvas);
}
在 Gson 中非常简单。
public final class MapToListTypeAdapterFactory
implements TypeAdapterFactory {
private MapToListTypeAdapterFactory() {
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final Type elementType = typeToken.getType() instanceof ParameterizedType
? ((ParameterizedType) typeToken.getType()).getActualTypeArguments()[0]
: Object.class;
final TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
final TypeAdapter<List<Object>> listTypeAdapter = new TypeAdapter<List<Object>>() {
@Override
public void write(final JsonWriter out, final List<Object> value) {
throw new UnsupportedOperationException();
}
@Override
public List<Object> read(final JsonReader in)
throws IOException {
in.beginObject();
final ArrayList<Object> list = new ArrayList<>();
while ( in.hasNext() ) {
final int index = Integer.parseInt(in.nextName());
final Object element = elementTypeAdapter.read(in);
ensureSize(list, index + 1);
list.set(index, element);
}
in.endObject();
return list;
}
};
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) listTypeAdapter
.nullSafe();
return typeAdapter;
}
//
private static void ensureSize(final ArrayList<?> list, final int size) {
list.ensureCapacity(size);
while ( list.size() < size ) {
list.add(null);
}
}
}
上面的类型适配器工厂做了以下事情:
- 检查目标类型是否为
List
(它不适用于链表,但可以简化); - 提取目标列表的类型参数,从而提取其元素类型并解析相应的类型适配器;
- 用地图读取适配器替换原始类型适配器,该适配器读取地图键并将它们假定为新列表索引(并在必要时扩大结果列表)并使用原始元素类型适配器读取每个地图值。请注意,列表可能具有稀疏索引的假设是易受攻击的,我只是出于演示目的将其放置(例如,输入 JSON 声明了一个单元素映射,其唯一索引“999999”显着扩大了列表) , 因此您可以仅使用
add(element)
方法忽略index
值。
只需将 Canvas
class 中的 pointData
字段注释为 @JsonAdapter(MapToListTypeAdapterFactory.class)
即可。