All json parsing

In our daily coding work, we often formulate or encounter such JSON structures

{
  "resultcode": "200"."reason": "Successful return."."result": {
    "area": "Pingyang County, Wenzhou City, Zhejiang Province"."sex": "Male"."birthday": "March 08, 1989."}}Copy the code

For the provider of this interface, the outermost ResultCode Reason and Result elements are present. So we can define a Response class like this

public class Response<T> {
    private String resultcode;

    private String reason;

    private T result;

}
Copy the code

Use generics to determine what result ends up filling into it. Of course, what we’re filling in here is an object, but we could also define it as the User class

public class User {
    private String area;
    private String sex;
    private String birthday;
}
Copy the code

This way, our data can be returned to the caller following the convention rules of the sample JSON. As the caller, the machine that receives this data, we need to define the following class ResopnseUser

public class ResopnseUser {
    private String resultcode;

    private String reason;

    private User result;
}

public class User {
    private String area;
    private String sex;
    private String birthday;
}
Copy the code

Then we can use fastJson for fun parsing

        String  testJson = "{\" the resultcode \ ": \" 200 \ "and \" "reason \" : \ "successful return \", \ "result \" : {\ "area \" : \ "zhejiang wenzhou pingyang county \", \ "sex \" : \ "m \", \ "birthday \" : \ "March 08, 1989 Day \ "}}";
        ResponseUser response1 =  JSONObject.parseObject(testJson,ResponseUser.class);

Copy the code

And as you can see, we did get what we wanted

Think of improvement measures

But there is a less serious consequence. For every interface I interconnect with a provider, I have to generate a corresponding class. Over time, these specialized classes become more numerous and more deeply nested. Since the provider can abstract out a Response class, can I do the same for the provider?

        String  testJson = "{\" the resultcode \ ": \" 200 \ "and \" "reason \" : \ "successful return \", \ "result \" : {\ "area \" : \ "zhejiang wenzhou pingyang county \", \ "sex \" : \ "m \", \ "birthday \" : \ "March 08, 1989 Day \ "}}";
        Response<User> response =  JSONObject.parseObject(testJson,Response.class);
Copy the code

Unfortunately, we did not get the result we expected. Only the outermost data was parsed successfully, and the inner result was still a JSONObject.

Turn to the almighty forum

Fortunately, there are not only thousands of engineers who have come up with this solution, and there are already people who have solved this problem! Through a variety of searches, we found that fastJson provides a TypeReference class that solves our problem.

String  testJson = "{\" the resultcode \ ": \" 200 \ "and \" "reason \" : \ "successful return \", \ "result \" : {\ "area \" : \ "zhejiang wenzhou pingyang county \", \ "sex \" : \ "m \", \ "birthday \" : \ "March 08, 1989 Day \ "}}";
        Response<User> response =  JSONObject.parseObject(testJson,new TypeReference<Response<User>>(){});
Copy the code

Don’t stop there

Wait, there’s nothing wrong with that? Generics perform type erasure at compile time! Response

Response = jsonObject.parseObject (testJson, response.class); Is invalid. So, even if we say new TypeReference

>(){} wouldn’t that get erased? How does it get the actual type we defined at run time? By looking at the source code of TypeReference, fortunately the source code is very simple and less code. The constructor protected TypeReference() is the first thing to come in, and we debug that we get the generic we wrote on the second line of code.

        Type superClass = getClass().getGenericSuperclass();

        Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
Copy the code

This code is simple enough to get its parent getGenericSuperclass() to get the actual type. Following the code, we can see that it calls a native method private Native String getGenericSignature0(); Get some information about ClassRepository and Type for this class and its parent. New TypeReference

>(){} creates an anonymous inner class of TypeReference. GetGenericSuperclass () gets the actual type information from the inner class of TypeReference.

Continue to dig

Let’s review our results now, with two notes:

  1. Generics perform type erasure at compile time.
  2. Gets its parent classgetGenericSuperclass()I get the actual type. Wait a minute. Those two things contradict each other. One says no, and two says it can still be found. What’s going on? We found the answer by looking at the compiled bytecode file.

    This is the include codeResponse<User> response = JSONObject.parseObject(testJson,new TypeReference<Response<User>>(){});Decompiled information from the class file of the method. We can see it has one moreLocalvariableTypeTableThe inside of the,SignatureStores the actual type information. ** This means that the type is not completely erased, and we can still get the type of the parameter by reflection. ** the so-called erase, just put the methodcodeProperty to erase bytecode. In addition, we saw thatInnerClassesIn this list, we have an inner class called Main$1, which is our own classnew TypeReference<Response<User>>(){}

conclusion

  1. Can be achieved byJSONObject.parseObject(testJson,new TypeReference<Response<User>>(){});To solve the problem of nested generics in JSON. Convenient, fast and intuitive
  2. Can be achieved bygetClass().getGenericSuperclass();Get the real type. The validation code
SubIntMap = new HashMap<String,Integer>(){}; // This class creates an anonymous subclass of HashMap. System.out.println(subIntMap.getClass().getSuperclass()); Type subClassType = subIntMap.getClass().getGenericSuperclass();if(subClassType instanceof ParameterizedType){
            ParameterizedType p = (ParameterizedType) subClassType;
            for(Type t : p.getActualTypeArguments()){ System.out.println(t); }}Copy the code

The output

class java.util.HashMap
class java.lang.String
class java.lang.Integer
Copy the code
  1. The type is not completely erased. You can get the type of the parameter by reflection. The validation code
 ResponseUser response = new ResponseUser();
        Field field = response.getClass().getField("result");
        System.out.println("result Type is "+ field.getType());
        System.out.println("result GenericType is "+ field.getGenericType());
public class ResponseUser {
    private String resultcode;

    private String reason;

    public List<User> result;
}
Copy the code

The output

result Type is interface java.util.List
result GenericType is java.util.List<com.jd.jr.alpha.cpa.fastJson.User>
Copy the code