1. 概述
在这个教程中,我们将讨论如何将一个JSON数组转换为等效的java.util.List
对象。Google提供的Gson库是一个Java库,它帮助我们将JSON字符串转换为Java对象,反之亦然。
这个库中的Gson
类有一个方法fromJson()
,它接受两个参数:第一个是JSON字符串,第二个是java.lang.reflect.Type
类型的参数。该方法会将JSON字符串转换为由其第二个参数表示的Java对象类型。
我们将创建一个通用方法,如convertJsonArrayToListOfAnyType(String jsonArray, T elementType)
,它可以将JSON数组转换为List<T>
,其中T
是List
元素的类型。
让我们深入了解这一点。
2. 问题描述
假设我们有两个JSON数组,一个是关于Student
的,另一个是关于School
的:
final String jsonArrayOfStudents =
"["
+ "{\"name\":\"John\", \"grade\":\"1\"}, "
+ "{\"name\":\"Tom\", \"grade\":\"2\"}, "
+ "{\"name\":\"Ram\", \"grade\":\"3\"}, "
+ "{\"name\":\"Sara\", \"grade\":\"1\"}"
+ "]";
final String jsonArrayOfSchools =
"["
+ "{\"name\":\"St. John\", \"city\":\"Chicago City\"}, "
+ "{\"name\":\"St. Tom\", \"city\":\"New York City\"}, "
+ "{\"name\":\"St. Ram\", \"city\":\"Mumbai\"}, "
+ "{\"name\":\"St. Sara\", \"city\":\"Budapest\"}"
+ "]";
通常,我们可以使用Gson
类将数组转换为List
对象:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingTypeToken() {
Gson gson = new Gson();
TypeToken<List<Student>> typeTokenForListOfStudents = new TypeToken<List<Student>>(){};
TypeToken<List<School>> typeTokenForListOfSchools = new TypeToken<List<School>>(){};
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, typeTokenForListOfStudents.getType());
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, typeTokenForListOfSchools.getType());
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我们为List<Student>
和List<School>
创建了TypeToken
对象。最后,使用这些TypeToken
对象,我们得到了Type
对象,并成功地将JSON数组转换为了List<Student>
和List<School>
。
为了促进重用,让我们尝试创建一个带有一个方法的通用类,该方法可以接收List
元素的类型并返回Type
对象:
class ListWithDynamicTypeElement<T> {
Type getType() {
TypeToken<List<T>> typeToken = new TypeToken<List<T>>(){};
return typeToken.getType();
}
}
对于给定的元素类型T
,我们实例化了一个泛型的TypeToken<List<T>>
。然后,我们返回相应的Type
对象。列表的元素类型只在运行时可用。
让我们看看这个方法的实际应用:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingTypeTokenFails() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, new ListWithDynamicTypeElement<Student>().getType());
assertFalse(studentsLst.get(0) instanceof Student);
assertThrows(ClassCastException.class, () -> studentsLst.forEach(e -> assertTrue(e instanceof Student)));
}
尽管方法编译通过,但在运行时却失败了,当我们试图遍历studentLst
时,会抛出ClassCastException
。此外,我们看到列表中的元素并非Student
类型。
3. 使用TypeToken.getParameterized()
解决方案
在Gson 2.10版本中,TypeToken
类新增了getParameterized()
方法(https://javadoc.io/static/com.google.code.gson/gson/2.10.1/com.google.gson/com/google/gson/reflect/TypeToken.html#getParameterized\(java.lang.reflect.Type,java.lang.reflect.Type...\)),这使得开发者能够处理在编译时无法获取列表元素类型的情况。
让我们看看这个新方法如何帮助我们获取参数化类的Type
信息:
Type getGenericTypeForListFromTypeTokenUsingGetParameterized(Class elementClass) {
return TypeToken.getParameterized(List.class, elementClass).getType();
}
当getParameterized()
方法在运行时被调用时,它会返回List
对象的实际类型及其元素类型。这将有助于Gson
类根据元素的正确类型信息成功将JSON数组转换为对应的List
对象。
现在,让我们看看方法的使用:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingGetParameterized() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, getGenericTypeForListFromTypeTokenUsingGetParameterized(Student.class));
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, getGenericTypeForListFromTypeTokenUsingGetParameterized(School.class));
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我们使用getGenericTypeForListFromTypeTokenUsingGetParameterized()
方法获取List<Student>
和List<School>
的Type
信息。最终,使用fromJson()
方法,我们成功将JSON数组转换为它们各自的Java List
对象。
4. 使用JsonArray
解决方案
Gson
库有一个JsonArray
类来表示JSON数组。我们将使用它来将JSON数组转换为List
对象:
<T> List<T> createListFromJsonArray(String jsonArray, Type elementType) {
Gson gson = new Gson();
List<T> list = new ArrayList<>();
JsonArray array = gson.fromJson(jsonArray, JsonArray.class);
for(JsonElement element : array) {
T item = gson.fromJson(element, elementType);
list.add(item);
}
return list;
}
首先,我们使用Gson
类的常规fromJson()
方法将JSON数组字符串转换为JsonArray
对象。然后,我们将JsonArray
对象中的每个JsonElement
元素转换为目标类型,这是createListFromJsonArray()
方法的第二个参数elementType
定义的。
将这些转换后的元素放入List
中,最后在方法结束时返回。
现在,让我们看看方法的使用:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingJsonArray() {
List<Student> studentsLst = createListFromJsonArray(jsonArrayOfStudents, Student.class);
List<School> schoolLst = createListFromJsonArray(jsonArrayOfSchools, School.class);
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我们使用createListFromJsonArray()
方法成功地将学生和学校的两个JSON数组转换为List<Student>
和List<School>
。
5. 使用Guava的TypeToken
解决方案
类似于Gson
库中的TypeToken
类,Guava库中的TypeToken
类也允许我们在运行时捕获泛型类型【注:由于Java的类型擦除机制,这在Java中是不可能的,除非使用反射(/guava-reflection)】。
让我们使用Guava的TypeToken
类实现一个示例:
<T> Type getTypeForListUsingTypeTokenFromGuava(Class<T> type) {
return new com.google.common.reflect.TypeToken<List<T>>() {}
.where(new TypeParameter<T>() {}, type)
.getType();
}
where()
方法通过将类型参数替换为变量type
中的类返回一个TypeToken
对象。
最后,让我们实际操作一下:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingTypeTokenFromGuava() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, getTypeForListUsingTypeTokenFromGuava(Student.class));
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, getTypeForListUsingTypeTokenFromGuava(School.class));
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
同样,我们使用fromJson()
方法将JSON数组转换为相应的List
对象。不过,我们借助Guava库在getTypeForListUsingTypeTokenFromGuava()
方法中获得了Type
对象。
6. 使用Java反射API的ParameterizedType
解决方案
ParameterizedType
是Java反射API的一部分,它用于表示像Collection<String>
这样的参数化类型。
让我们实现ParamterizedType
,以代表具有参数化的任何类:
public class ParameterizedTypeImpl implements ParameterizedType {
private final Class<?> rawType;
private final Type[] actualTypeArguments;
private ParameterizedTypeImpl(Class<?> rawType, Type[] actualTypeArguments) {
this.rawType = rawType;
this.actualTypeArguments = actualTypeArguments;
}
public static ParameterizedType make(Class<?> rawType, Type ... actualTypeArguments) {
return new ParameterizedTypeImpl(rawType, actualTypeArguments);
}
@Override
public Type[] getActualTypeArguments() {
return actualTypeArguments;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return null;
}
}
变量actualTypeArguments
存储类型参数的类信息,而rawType
表示泛型类。make()
方法返回参数化类的Type
对象。
最后,让我们实际操作一下:
@Test
void givenJsonArray_whenListElementTypeDynamic_thenConvertToJavaListUsingParameterizedType() {
Gson gson = new Gson();
List<Student> studentsLst = gson.fromJson(jsonArrayOfStudents, ParameterizedTypeImpl.make(List.class, Student.class));
List<School> schoolLst = gson.fromJson(jsonArrayOfSchools, ParameterizedTypeImpl.make(List.class, School.class));
assertAll(
() -> studentsLst.forEach(e -> assertTrue(e instanceof Student)),
() -> schoolLst.forEach(e -> assertTrue(e instanceof School))
);
}
我们使用make()
方法获取参数化的List
类的Type
信息,并成功地将JSON数组转换为它们对应的List
对象形式。
7. 总结
在这篇文章中,我们讨论了四种不同的方法,用于在运行时获取List<T>
对象的Type
信息。最后,我们利用Gson
库的fromJson()
方法,使用Type
对象将JSON数组转换为List
对象。
由于所有这些方法最终都调用了fromJson()
,因此它们的性能相近。然而,TypeToken.getParameterized()
方法最为简洁,我们建议使用它。
如往常一样,使用的代码可在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/json-modules/gson-2。