我定义“回复”,“人”,“课程”类:
class Response {
private int countA; // record the length of data
private List<Person> data;
// getter & setter
}
class Person {
private String name;
private int age;
private int countB; // record the length of list
private List<Course> list;
// getter & setter
}
class Course {
private String name;
// getter & setter
}
我有一个json字符串(从某个服务器返回),如下所示:
{
"countA":1,
"data":
{
"name":"peter",
"age":18,
"countB":1,
"list":
{
"name":"math"
}
}
}
但是当“countA”&gt; 1或“countB”&gt; 1,它会是这样的:
{
"countA":2,
"data":
[ // when countA > 1, it have this one '['
{
"name":"amy",
"age":17,
"countB":1,
"list":
{
"name":"music"
}
},
{
"name":"david",
"age":16,
"countB":2,
"list":
[ // when countB > 1, it have this one '['
{
"name":"music"
},
{
"name":"computer"
}
] // when countB > 1, it have this one ']'
}
] // when countA > 1, it have this one ']'
}
所以,我想自定义我的反序列化器,只需添加两个字符'['&amp;当countA或countB = 1时,']'到数据或列表:
class MyDeserializer implements JsonDeserializer<List>{
public List deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
String jsonString = json.toString();
// fix json string to my expected format
if(!(jsonString.startsWith("[") && jsonString.endsWith("]")))
jsonString = "["+jsonString+"]";
// get the XXX type from List<XXX>
Type valueType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
// call default deserializer
return context.deserialize(new Gson().fromJson(jsonString,JsonElement.class), valueType);
}
}
在我的主要():
String jsonStrng = ...; // return from some server
Response bean = (Response) fromJson(jsonString, Response.class); // get the following error
方法fromJson():
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object fromJson(String data, Class clazz) {
Gson gson = new GsonBuilder().registerTypeAdapter(List.class, new MyDeserializer()) .create();
Object obj = gson.fromJson(data, clazz);
gson = null;
return obj;
}
但我有错误:
Expected BEGIN_OBJECT but was BEGIN_ARRAY
我知道我收到此错误的原因:
但是,如果我尝试再次调用我的反序列化器,而不是调用默认的反序列化器,则会导致无限循环。
我的问题是如何修复它?
我在网上搜索了很长时间。但没用。请帮助或尝试提供一些如何实现这一目标的想法。
提前致谢。
答案 0 :(得分:0)
Gson是一个很棒的库,以高效的方式提供了许多功能,我认为出于几个原因,我们不应该劝阻您当前的方法。让我们仔细看看。
String jsonString = json.toString();
toString()
基本上用于调试目的,我们不鼓励使用它,除了像StringBuilder
这样的类,其中toString
是获取结果的唯一方法设计。结果字符串可能看起来像你真正想要的那样,但这个结果可能很昂贵而且并不总是准确的(甚至将来也会改变)。
if(!(jsonString.startsWith("[") && jsonString.endsWith("]")))
jsonString = "["+jsonString+"]";
这是性能和内存消耗的另一个惩罚点:连接一个大字符串将导致更大的字符串,而第一个字符串仍将消耗内存。请注意,JsonDeserializer
为您提供了一个随时可用的JsonElement
,它有一个非常方便的API来检查JSON树(内存中的JSON对象表示)。
Type valueType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
这里可能是ClassCastException
:给定的类型不一定要参数化,因此它可能不是ParameterizedType
实例,因为泛型类型在Java中的工作方式。
return context.deserialize(new Gson().fromJson(jsonString,JsonElement.class), valueType);
从头开始创建新的Gson
实例时,您在这里失去了Gson
配置,因此它可能无法以预期的方式工作。此外,Gson
实例化是相对昂贵的操作,通常不需要重新实例化。
如何重做:
// Use generic types for good
final class UnwrappedElementListDeserializer<T>
implements JsonDeserializer<List<T>> {
// This deserializer holds no state, therefore it can be instantiated once...
private static final JsonDeserializer<?> unwrappedElementListDeserializer = new UnwrappedElementListDeserializer<>();
private UnwrappedElementListDeserializer() {
}
// But any caller won't ever know it unless the caller checks instance references equality with ==. But any reason of doing that?
static <T> JsonDeserializer<List<T>> getUnwrappedElementListDeserializer() {
@SuppressWarnings({ "unchecked", "rawtypes" })
final JsonDeserializer<List<T>> castUnwrappedElementListDeserializer = (JsonDeserializer) unwrappedElementListDeserializer;
return castUnwrappedElementListDeserializer;
}
@Override
public List<T> deserialize(final JsonElement rootJsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
// Gson uses JsonNull to denote the JSON null value -- it has API, whilst Java null can never have its "API"...
if ( rootJsonElement.isJsonNull() ) {
// Empty lists are better than null
// Note that Collections.emptyList() might be an option too, but Gson uses mutable lists (you can modify them), so we do either
// emptyList() cannot be modified, so we just return a new modifiable list
return new ArrayList<>();
}
// Is it just an object? It looks like a single element out of an array
final Type elementType = tryGetTypeParameter0(type);
if ( rootJsonElement.isJsonObject() ) {
final T element = context.deserialize(rootJsonElement, elementType);
// Collections.singletonList() is immutable too
final List<T> list = new ArrayList<>();
list.add(element);
return list;
}
// If it's an array, then it must have two or more elements
if ( rootJsonElement.isJsonArray() ) {
// context.deserialize(jsonElement, type) cannot work due to infinite self-recursion
final List<T> list = new ArrayList<>();
for ( final JsonElement jsonElement : rootJsonElement.getAsJsonArray() ) {
final T element = context.deserialize(jsonElement, elementType);
list.add(element);
}
return list;
}
throw new JsonParseException("Cannot parse: " + rootJsonElement);
}
private static Type tryGetTypeParameter0(final Type type) {
if ( type instanceof ParameterizedType ) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getActualTypeArguments()[0];
}
return Object.class;
}
}
数据传输对象:
final class Response {
// Gson can assign final fields
// Primitive types like `int` cannot be `null`-ed, but simple `0` will be inlined by javac
// So `Integer.valueOf(0)` is a kind of cheating to avoid constant inlining
final int countA = Integer.valueOf(0);
final List<Person> data = null;
// Yep, just for debugging purposes
@Override
public String toString() {
return new StringBuilder("Response{")
.append("countA=").append(countA)
.append(", data=").append(data)
.append('}')
.toString();
}
}
final class Person {
final String name = null;
final int age = Integer.valueOf(0);
final int countB = Integer.valueOf(0);
final List<Course> list = null;
@Override
public String toString() {
return new StringBuilder("Person{")
.append("name='").append(name).append('\'')
.append(", age=").append(age)
.append(", countB=").append(countB)
.append(", list=").append(list)
.append('}')
.toString();
}
}
final class Course {
final String name = null;
@Override
public String toString() {
return new StringBuilder("Course{")
.append("name='").append(name).append('\'')
.append('}')
.toString();
}
}
以下演示,其中JSON_1
和JSON_2
代表您的JSON文档
public static void main(final String... args) {
final Gson gson = new GsonBuilder()
.registerTypeAdapter(List.class, getUnwrappedElementListDeserializer())
.create();
System.out.println(gson.fromJson(JSON_1, Response.class));
System.out.println(gson.fromJson(JSON_2, Response.class));
}
将具有以下输出:
回复{countA = 1,数据= [人{名=&#39;彼得&#39;,年龄= 18,countB = 1,列表= [课程{名称=&#39;数学&#39;}] }]}
回复{countA = 2,数据= [人{姓名=&#39; amy&#39;,年龄= 17,countB = 1,列表= [课程{名称=&#39;音乐&#39;}]},人{name =&#39; david&#39;,age = 16,countB = 2,list = [课程{name =&#39;音乐&#39;},课程{name =&#39; computer&#39;} ]}]}
对您的代码提出更多评论。
Gson
可以实例化一次,原因很多,包括线程安全:只需将其置于静态字段中(假设fromJson
也是static
)。gson = null
没有多大帮助 - 你可以删除这一行。fromJson
可以非常轻松地重新实施:public static <T> T fromJson(String data, Type type) { return gson.fromJson(data, type); }
- 所以您不需要@SupressWarnings
,您可以在呼叫网站上摆脱类型转换,以及通过传递Type
,传递任何类型,而不仅仅是Class<T>
实例唯一能够容纳的真实类型。为了简单起见,甚至可以删除这种方法?.registerTypeAdapter(TypeToken.getParameterized(List.class, Course.class).getType(), getUnwrappedElementListDeserializer())
不触及其他类参数化(这是Class<T>
通常无法做到的)。但是,即使这可能会影响您想要的(比如,您希望某些类具有此类行为,但不要让其他类具有此行为):请查看@JsonAdapter
注释。Reader
实例传递给gson.fromJson()
方法更有效,而不是要求将整个文档存储在内存中。