When using springmvc, we usually define generic classes like this to interact with the front end, so that the front end can do some unified processing:
public class Result<T> { private int ret; private String msg; private T data; // getter and setter methods are omitted here }
After such a class is serialized into json, js deserialization is handled without pressure. But if the consumer of the rest interface is java, the type erasure of java generics is easy to introduce some obstacles.
An iteration of deserialization
First define a class, which will be used in the following examples:
public class Item { private String name; private String value; // getter and setter methods are omitted here }
JSON data:
{ "data":{ "name":"username", "value":"root" }, "msg":"Success", "ret":0 }
When we get the above data, we think that the corresponding type is result<item>, so we have to find a way to deserialize the json data into this type.
v1
JSONObject. parseObject(json, Result<Item>.class);, The compiler reported an error of Cannot select parameterized type.
v2
JSONObject.parseObject(json, Result.class);, Execution is OK. But without Item type information, fastjson can't know you well enough to know that you should change data to Item type, result getData(). Getclass() results in com alibaba. fastjson. Jsonobject is a good deal.
v3
After looking for the previous experience, we use typereference to deal with jsonobject parseObject(json, new TypeReference<Result<Item>>(){});, Finally "perfect" solution!
v4
With the experience of v3, I thought I had found a shortcut to general processing, so I encapsulated a tool method to deal with this type:
private static <T> Result<T> parseResultV1(String json) { return JSONObject.parseObject(json, new TypeReference<Result<T>>() { }); }
This parseResult method is used where v3 is used:
Result<Item> result = parseResultV1(json);
Thinking everything was fine, I submitted the code without even testing. Without testing, of course, it is difficult to have good results:
System.out.println(result.getData()); // java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to Item
It is obvious that parseResultV1 has lost the type information of Item.
{ "data":"Hello,world!", "msg":"Success", "ret":0 }
Try the Result form. parseResultV1 can successfully deserialize it. Presumably (without looking at the specific implementation of fastjson), fastjson just detected that the data field is of String type and assigned it to the data field. Looking closely, parseObject does not report an error, but it reports an error in getData(). In connection with the generic erasure of java, we are using getData(), and we should treat data as an Object type:
String data = (String)result.getData(); System.out.println(data);
v5
The constructor of TypeReference can pass in parameters,
private static <T> Result<T> parseResultV2(String json, Class<T> clazz) { return JSONObject.parseObject(json, new TypeReference<Result<T>>(clazz) { }); }
This can really deserialize the result<item> perfectly.
v6
Later, it was found that parseResultV2 could not handle similar results<list<t>>, and the original TypeReference could not handle nested generics (here it means that the type parameters are not determined, rather than similar results<list<item>> type parameters have been determined). borrow Several ways of Fastjson parsing multi-level generics - using class files to parse multi-level generics A new method specially dealing with List type is added:
private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) { return JSONObject.parseObject(json, buildType(Result.class, List.class, Item.class)); } private static Type buildType(Type... types) { ParameterizedTypeImpl beforeType = null; if (types != null && types.length > 0) { for (int i = types.length - 1; i > 0; i--) { beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]); } } return beforeType; }
Or according to the fact that there are only two layers here, it is simple as follows:
private static <T> Result<List<T>> parseListResult(String json, Class<T> clazz) { ParameterizedTypeImpl inner = new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class); ParameterizedTypeImpl outer = new ParameterizedTypeImpl(new Type[]{inner}, null, Result.class); return JSONObject.parseObject(json, outer); }
v7
todo: the above two methods can already meet the existing needs. Let's see if we can unify the two methods into one when we have time.
com.alibaba.fastjson.TypeReference
Look at the source code of TypeReference:
protected TypeReference(Type... actualTypeArguments) { Class<?> thisClass = this.getClass(); Type superClass = thisClass.getGenericSuperclass(); ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0]; Type rawType = argType.getRawType(); Type[] argTypes = argType.getActualTypeArguments(); int actualIndex = 0; for(int i = 0; i < argTypes.length; ++i) { if (argTypes[i] instanceof TypeVariable) { argTypes[i] = actualTypeArguments[actualIndex++]; if (actualIndex >= actualTypeArguments.length) { break; } } } Type key = new ParameterizedTypeImpl(argTypes, thisClass, rawType); Type cachedType = (Type)classTypeCache.get(key); if (cachedType == null) { classTypeCache.putIfAbsent(key, key); cachedType = (Type)classTypeCache.get(key); } this.type = cachedType; }
In fact, it first gets the generic Type parameter argTypes, and then iterates over these Type parameters. If it is a TypeVariable Type, it will be replaced by the Type passed in by the constructor. Then the argTypes after this processing will construct a new Type based on parametrizedtypeimpl, so that the new Type can have the information of each generic Type parameter of the Type we expect. So fastjson can deserialize the result<item> as we expect.
Because of this processing logic, the result<list<t>> in v6 cannot be processed. It can only handle single-layer multi type parameters, but cannot handle nested generic parameters.
There is no formal document about the usage of the parameter constructor of TypeReference, but based on the understanding of the source code, we should use the parameter constructor of TypeReference in this way:
new TypeReference<Map<T1, T2>>(clazz1, clazz2){} new TypeReference<Xxx<T1, T2, T3>>(clazz1, clazz2, clazz3){}
That is, the Type list in the constructor should correspond to the generic Type parameters one by one.
com.alibaba.fastjson.util.ParameterizedTypeImpl
What about ParameterizedTypeImpl?
import java.lang.reflect.ParameterizedType; // ... Other omissions public class ParameterizedTypeImpl implements ParameterizedType { public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){ this.actualTypeArguments = actualTypeArguments; this.ownerType = ownerType; this.rawType = rawType; } // ... Other omissions }
I haven't known parametrizedtype before, and it is related to
Type All known sub interfaces: GenericArrayType, ParameterizedType, TypeVariable<D>, WildcardType All known implementation classes: Class
Take a look at the ParameterizedType interface that has been used this time (the following comments are copied from the jdk Chinese document, which is not easy to understand)
public interface ParameterizedType extends Type { //Returns an array of Type objects that represent the actual Type parameters of this Type. //Note that in some cases, the returned array is empty. This can happen if this type represents a non parametric type nested within a parametric type. Type[] getActualTypeArguments(); //Returns a Type object indicating that this Type is one of its members. Type getOwnerType(); //Returns a Type object that represents the class or interface that declares this Type. Type getRawType(); }
Understand with the example of ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType):
new ParameterizedTypeImpl(new Type[]{clazz}, null, List.class) is used to construct list<t>.
About Type
Generic Type is a new feature of Java SE 1.5, and Type is only available in 1.5. It was introduced to extend types after java added generics. Some classes or interfaces related to Type represent some Type information similar to Class but lost due to generic erasure.
Parametrizedtypeimpl use step pits
See: Memory leak caused by improper use of fastjson deserialization
This blog( liqipeng )Unless it is clearly stated to reprint, otherwise it is liqipeng Original or organized, please keep this link for Reprint: fastjson deserialization of multi-level nested generic classes and Type types in java - liqipeng - blog Garden.
This blog( liqipeng )Unless it is clearly stated to reprint, otherwise it is liqipeng Original or organized, please keep this link for Reprint: fastjson deserialization of multi-level nested generic classes and Type types in java - liqipeng - blog Garden.