This article corresponding project is MultiTypeJsonParser , the project addresses https://github.com/sososeen09/MultiTypeJsonParser
0 prelude
Use Gson to parse json should be very common, and in most cases we simply create a Gson objects, according to json and then the corresponding Java classes to parse it.
Gson gson = new Gson();
Person person = gson.form(json,Person.class);
However, for more complex JSON, following this example, attributes corresponding jsonObject the field is completely different, this time to simply use the above method can not resolve the.
{
"total": 2, "list": [ { "type": "address", "attributes": { "street": "NanJing Road", "city": "ShangHai", "country": "China" } }, { "type": "name", "attributes": { "first-name": "Su", "last-name": "Tu" } } ] }
Of course, we can not say that one-step way to solve, but with a little stupid method is still possible. Example, to manually get parsed attributes corresponding jsonObject, according to the corresponding type at the same level it can determine this value corresponding to a period jsonObject Java class which, finally using gson.from()
methods parsed Java objects corresponding to attributes.
ListInfoWithType listInfoWithType = new ListInfoWithType(); //创建 org.json 包下的 JSONObject 对象 JSONObject jsonObject = new JSONObject(TestJson.TEST_JSON_1); int total = jsonObject.getInt("total"); //创建 org.json 包下的 JSONArray 对象 JSONArray jsonArray = jsonObject.getJSONArray("list"); Gson gson = new Gson(); List<AttributeWithType> list = new ArrayList<>(); //遍历 for (int i = 0; i < jsonArray.length(); i++) { JSONObject innerJsonObject = jsonArray.getJSONObject(i); Class<? extends Attribute> clazz; String type = innerJsonObject.getString("type"); if (TextUtils.equals(type, "address")) { clazz = AddressAttribute.class; } else if (TextUtils.equals(type, "name")) { clazz = NameAttribute.class; } else { //有未知的类型就跳过 continue; } AttributeWithType attributeWithType = new AttributeWithType(); //采用Gson解析 Attribute attribute = gson.fromJson(innerJsonObject.getString("attributes"), clazz); attributeWithType.setType(type); attributeWithType.setAttributes(attribute); list.add(attributeWithType); } listInfoWithType.setTotal(total); listInfoWithType.setList(list);
While this can be achieved deserialization json whole, but this way is too much trouble, but is not elegant, if there are many such cases in the project, we will do a lot of repetitive manual labor.
How to be more elegant, more general solution to this problem, not find the answer on the Internet, I had to go in-depth look at the Gson. With this purpose, look at the Gson document , I found a word
Gson can work with arbitrary Java objects including pre-existing objects that you do not have source code of.
Remark Gson can handle arbitrary Java objects. So to speak above the kind of situation in terms of de-serialization, Gson should be able to do it. By studying Gson documents, it can be found by customizing JsonDeserializer way to achieve this jsonObject resolve different type of situation.
We know that, in most cases Gson is created through direct new out of the way, but you can also GsonBuilder this class uses to generate Gson.
Gson gson = new GsonBuilder()
.registerTypeAdapter(Id.class, new IdTypeAdapter())
.enableComplexMapKeySerialization()
.serializeNulls()
.setDateFormat(DateFormat.LONG)
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.setPrettyPrinting()
.setVersion(1.0)
.create();
GsonBuilder by registerTypeAdapter () method, the target class to register. When serialization or deserialization target class when we will call typeAdapter registered, thus achieving a serialization and de-serialization process Gson of human intervention.
The second parameter GsonBuilder of registerTypeAdapte () method is of type Object, which means we can register multiple types of typeAdapter, currently supported types are JsonSerializer, JsonDeserializer, InstanceCreator, TypeAdapter.
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter)
After some fiddle, I wrote a utility class for the complex json above, with less than 10 lines of code to get, but also more elegant and versatile.
MultiTypeJsonParser<Attribute> multiTypeJsonParser = new MultiTypeJsonParser.Builder<Attribute>() .registerTypeElementName("type") .registerTargetClass(Attribute.class) .registerTargetUpperLevelClass(AttributeWithType.class) .registerTypeElementValueWithClassType("address", AddressAttribute.class) .registerTypeElementValueWithClassType("name", NameAttribute.class) .build(); ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);
In this paper, a simple analysis of how to resolve the complex type json custom JsonDeserializer to achieve a universal tool class. For after the encounter similar problems, this approach may provide a problem-solving ideas. Specific code and examples, you can view the project . If you have some inspiration for your ideas, please share and Star.
1 JsonDeserializer Introduction
JsonDeserializer is an interface, use of the time required to implement this interface and to register for specific types GsonBuilder in. When deserializing corresponding to this class will invoke the custom JsonDeserializer Deserialize () method. Next, several parameters of this method to do something to explain, in order to better understand the process Gson resolution.
public interface JsonDeserializer<T> { public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException; }
1.1 JsonElement
JsonElement representative of an element in Gson. It is an abstract class, there are four sub-categories: JsonObject, JsonArray, JsonPrimitive, JsonNull .
1.JsonObject represents json string containing the name-value type, where name is a string, and the value may be other types of JsonElement element. In json by "{}" wrapped up a whole is JsonObject. E.g
// "attributes" 是name,后面跟着的{}内容是它对应的value,而这个value就是一个JsonObject
"attributes": {
"first-name": "Su",
"last-name": "Tu"
}
2.JsonArray This class represents an array type in Gson in an array is a collection of JsonElement, this set may be different for each type. This is an ordered set, the order means that the element is to be maintained in. List corresponding to the above example, "[]" is wrapped up json JsonArray.
3. ** JsonPrimitive ** This value can be considered the original type json, comprising Java eight basic types and their corresponding package type, also contains the String type. Such as the above "first-name" corresponding to the "Su" is a type of JsonPrimitive String.
4.JsonNull by name may have guessed, this represents a null value.
1.2 Type
Type is the type of Java all top level interface, which subclasses have GenericArrayType, ParameterizedType, TypeVariable, WildcardType, are in the following classes java.lang.reflect package. In addition, we are most familiar with a class Class also implements Type interface.
In general, calling registerTypeAdapter GsonBuilder of () to register, the first argument of type Class using it.
1.3 JsonDeserializationContext
This class is in the process of anti-sequence, called by other classes JsonDeserialization of our custom deserialize () method when passed over in Gson it is the only one to achieve a private inner class GsonContextImpl TreeTypeAdapter in. You can be self deserialize JsonDeserializer defined () to call deserialize JsonDeserializationContext in the () method to get an object.
But remember, if passed to JsonDeserializationContext in json and json JsonDeserializer the same, may result in an infinite loop call.
2 ideas analysis
2.1 Creating JavaBean
Or in the json top analyzed in list corresponding to JsonArray, two of them in JsonObject, attributes corresponding JsonObject field is completely different, but for unity, the time to write JavaBean can set up a co-parent to them, Although it is empty.
public class Attribute {
... } public class AddressAttribute extends Attribute { private String street; private String city; private String country; ... 省略get/set } public class NameAttribute extends Attribute { @SerializedName("first-name") private String firstname; @SerializedName("last-name") private String lastname; ...省略get/set }
Set Attribute this SuperClass just to go register GsonBuilder, when we will be based on specific analysis
to find the corresponding Class type corresponding type.
gsonBuilder.registerTypeAdapter(Attribute.class, new AttributeJsonDeserializer());
To this we should think, type the corresponding value will definitely have to correspond to specific JavaBean up. For example, here is
"address"——AddressAttribute.class
"name"——NameAttribute.class
If the type is "address", then we can use gson get AddressAttribute.class and corresponding json to parse.
Attribute attribute = gson.form(addressJson,AddressAttribute.class);
2.2 How accurately into the corresponding json JavaBean
We are registered in the parent class Attribute, when deserialization will need to resolve Attribute the corresponding parameter as json callback custom JsonDeserializer. We can write the following methods to get their own logic Attribute object we need.
public Attribute deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
But should be careful of the friends found, json pass this time there might be something like
{
"street": "NanJing Road", "city": "ShangHai", "country": "China" }
There might be something like
{
"first-name": "Su", "last-name": "Tu" }
We know how to resolve AddressAttribute or NameAttribute? ? ?
We think, into which specific analysis, we definitely need to know the type corresponding value. And this type of field is the same level attributes, this is certainly not shining just want to get the value of.
We try to think, to know the value of this type corresponds to what is certainly attributes json on a level.
{
"type": "name",
"attributes": {
...
}
}
Can we go to register a typeAdapter in GsonBuilder to parse this json outer layer of it? of course can.
gsonBuilder.registerTypeAdapter(AttributeWithType.class, new AttributeWithTypeJsonDeserializer());
This is AttributeWithType json corresponding outer JavaBean
public class AttributeWithType {
private String type; private Attribute attributes; ... }
In deserialization AttributeWithType this class, we can get this type corresponding value, then the corresponding JsonDeserializer Attribute this value is passed to the inner layer. This can go on AddresAttribute NameAttribute or deserialized value is in accordance with the "address" or "name".
2.3 There is a pit
Then we talked about earlier, calling JsonDeserializationContext approach should pay attention to an infinite loop. In the specific practice, although I have no way to call JsonDeserializationContext, but still appeared in the case of an infinite loop. Because I was so used.
AttributeWithType attributeWithType = gson.fromJson(json, AttributeWithType.class);
At first glance, no problem ah, the problem lies in the gson body. This gson is already registered resolve AttributeWithType of GsonBuilder created. gson.fromJson () method is deserialized json json AttributeWithType corresponding, gson.fromJson () corresponding to internal calls AttributeWithType again in JsonDeserializer Deserialize () method, resulting in an infinite loop.
Avoid endless loop way is to use a new GsonBuilder gson, this GsonBuilder no longer registered AttributeWithType, but only registered Attribute go to resolve.
3 In order to better and more versatile
1. In the project, it will still exist json another format, there is no single type external elements, but with other elements in the same JsonObject in. This format is more easy, does not require registration typeAdaper to the outer layer.
{
"total": 2, "list": [ { "type": "address", "street": "NanJing Road", "city": "ShangHai", "country": "China" }, { "type": "name", "first-name": "Su", "last-name": "Tu" } ] } MultiTypeJsonParser<Attribute> multiTypeJsonParser = new MultiTypeJsonParser.Builder<Attribute>() .registerTypeElementName("type") .registerTargetClass(Attribute.class) // 如果所要解析的 jsonObejct 中已经含有能够表示自身类型的字段,不需要注册外层 Type,这样更省事 // .registerTargetUpperLevelClass(AttributeWithType.class) .registerTypeElementValueWithClassType("address", AddressAttribute.class) .registerTypeElementValueWithClassType("name", NameAttribute.class) .build(); ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);
2. If it is found that some types are not registered to MultiTypeJsonParser the Builder in the resolution process, the analysis of the encounter corresponding jsonObject direct return null. For example, such a json below, "type" corresponding to the "parents" If not registered, then when the deserialization json object is represented as null.
{
"type": "parents", "attributes": { "mather": "mi lan", "father": "lin ken" } }
Set in Android we deserialized after such a json object in general will get to the list control, if not registered before json returned by the backend contains the type of program in order not to crash, need to deserialize null objects filtration, the project provides a tool class ListItemFilter may be filtered to null the set of elements.
4 Conclusion
How elegant resolve this different type JsonObject, started thinking I was missing, the Internet has not found the right document. But by looking at Gson documentation and source code, through their own understanding and analysis, the gradual completion of this process. I have a feeling that, more than to see the official documents should use search solution is better than blindly go.
Code is the best documentation, this paper simply introduces some realization of ideas, some of the code in the text posted for the convenience of talk, and project code may have some differences. You can see the use of specific items in the example.
If you have questions, please mention the issue or message, if helpful to you, welcome to Star.