JSON解析之Gson

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。Gson是Google提供的开源库,可以将Java对象转换为JSON数据,也可以JSON数据转换为Java对象。本文简要介绍Gson库基本使用方法。

解析JSON

本例中使用的JSON数据是实际项目中通过luci获取到的,这里只是作为数据示例。

先以一段简单的Http Response的JSON数据开始:

1
2
3
4
5
{
"id": 1,
"result": "a5823b98e0dc91cecddb5516e7e85c85",
"error": null
}

那么它对应的Java对象可以表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Response {
private int id;

private String result;

private String error;

public int getId() {
return id;
}

public String getResult() {
return result;
}

public String getError() {
return error;
}

private Response() {
}
}

那么可以使用Gson将JSON数据解析为Response对象:

1
2
3
4
Response parse(String json) {
Gson gson = new Gson();
return gson.fromJson(json, Response.class);
}

对于有些代码规范需要将在变量名前面加上m前缀,那么变量名就和JSON字段名不一样了,这个时候就需要用到注解@SerializedName。将Response类修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Response {
@SerializedName("id")
private int mId;

@SerializedName("result")
private String mResult;

@SerializedName("error")
private String mError;

public int getId() {
return mId;
}

public String getResult() {
return mResult;
}

public String getError() {
return mError;
}

private Response() {
}
}

如果Response的JSON数据发生了变化:

1
2
3
4
5
6
7
8
{
"id": 2,
"result": null,
"error": {
"message": "Method not found.",
"code": -32601
}
}

可以看到error字段不为空了。现在需要修改mError的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Response {
...
@SerializedName("error")
private Error mError;

public Error getError() {
return mError;
}

public static class Error{
@SerializedName("message")
private String mMessage;
@SerializedName("code")
private int mCode;

public String getMessage() {
return mMessage;
}

public int getCode() {
return mCode;
}
}
...
}

这样不用修改parse方法也可以解析JSON数据为Response对象了。如果JSON数据result字段发生了变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"id": 2,
"result": [
{
"band": "5G",
"type": "mt7612e",
"vendor": "ralink",
"autoch": "2",
".index": 0,
"channel": "0",
".name": "mt7612e",
".type": "wifi-device",
".anonymous": false
},
{
"band": "2.4G",
"type": "mt7628",
"vendor": "ralink",
".index": 2,
"channel": "0",
".name": "mt7628",
"auotch": "2",
".type": "wifi-device",
".anonymous": false
}
],
"error": null
}

可以看到JSON数据的result是一个JSON数组,而前面的result是字符串,如果要兼容前面的JSON解析,首先要修改Response中的mResult

1
2
3
4
5
6
7
8
9
public class Response {
...
@SerializedName("result")
private JsonElement mResult;
...
public JsonElement getResult() {
return mResult;
}
}

其次,在Gson初步解析后需要判断mResult的具体类型来进一步解析:

1
2
3
4
5
6
7
8
9
10
11
void parse(String json, ParseCallback callback) {
Gson gson = new Gson();
Response response = gson.fromJson(json, Response.class);

JsonElement resultElement = response.getResult();
if (resultElement.isJsonPrimitive()) {
// parse string
} else if (resultElement.isJsonArray()) {
// parse array
}
}

由于解析的结果不止一种类型,所以这里用一个回调来返回不同类型的解析结果:

1
2
3
4
5
interface ParseCallback {
void onResult(String result);

void onResult(List<WifiDevice> devices);
}

其中WifiDevice对应于JSON数组中的具体类型,这里就不给出具体定义了。接下来看看进一步的解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void parse(String json, ParseCallback callback) {
...
if (resultElement.isJsonPrimitive()) {
String result = resultElement.getAsString();
if (callback != null) {
callback.onResult(result);
}
} else if (resultElement.isJsonArray()) {
List<WifiDevice> devices = new ArrayList<>();

JsonArray array = resultElement.getAsJsonArray();
for (JsonElement element : array) {
WifiDevice device = gson.fromJson(element, WifiDevice.class);
devices.add(device);
}
if (callback != null) {
callback.onResult(devices);
}
}
}

这里仅仅举例解析result字段是两种类型的情况,还有当result为不同元素类型的数组,这个时候可以定义不同的Response类(假设事先知道result字段的具体类型)。

生成JSON数据

相对于JSON解析,把类序列化为JSON数据就简单多了。如果有一个成绩单的类需要生成JSON数据,先来看一下Transcript类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Transcript {

@SerializedName("name")
private String mName;

@SerializedName("grade")
private int mGrade;

@SerializedName("class")
private int mClass;

@SerializedName("subject_scores")
private EnumMap<Subject, Integer> mSubjectScores = new EnumMap<Subject, Integer>(Subject.class);

private Transcript() {
}

public Transcript(String name, int grade, int clazz) {
mName = name;
mGrade = grade;
mClass = clazz;
}

public void addSubjectScore(Subject subject, int score) {
mSubjectScores.put(subject, score);
}

enum Subject {
CHINESE,
ENGLISH,
MATH,
PHYSICAL
}
}

接着通过Gson生成JSON数据:

1
2
3
4
5
6
7
Gson gson = new Gson();
Transcript transcript = new Transcript("Huntto", 3, 17);
transcript.addSubjectScore(Transcript.Subject.CHINESE, 80);
transcript.addSubjectScore(Transcript.Subject.ENGLISH, 85);
transcript.addSubjectScore(Transcript.Subject.MATH, 98);
transcript.addSubjectScore(Transcript.Subject.PHYSICAL, 83);
String json = gson.toJson(transcript, Transcript.class);

生成的JSON数据为:

1
2
3
4
5
6
7
8
9
10
11
{
"class": 17,
"grade": 3,
"name": "Huntto",
"subject_scores": {
"CHINESE": 80,
"ENGLISH": 85,
"MATH": 98,
"PHYSICAL": 83
}
}

虽然成功了,但是不知道能不能再次通过Gson解析:

1
2
3
4
5
6
Gson gson = new Gson();
Transcript transcript = new Transcript("Huntto", 3, 17);
...
String json = gson.toJson(transcript, Transcript.class);

Transcript parsedTranscript = gson.fromJson(json, Transcript.class);

解析不成功,报错了:

1
2
Caused by: java.lang.IllegalArgumentException: field me.huntto.gsondemo.Transcript.mSubjectScores has type java.util.EnumMap, got java.util.LinkedHashMap
at java.lang.reflect.Field.set(Native Method)

意思是不能用EnumMap,需要使用LinkedHashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Transcript {
...

@SerializedName("subject_scores")
private HashMap<String, Integer> mSubjectScores = new LinkedHashMap<>();

...

public void addSubjectScore(Subject subject, int score) {
mSubjectScores.put(subject.name(), score);
}
...
}

EnumMap换成LinkedHashMapHashMap都可以成功解析。

后记

好记性不如烂笔头。把每次用过的知识点总结一下记录下来,就不用每次需要使用的时候去百度或者Google(没准搜出来的都是广告)。

参考

[1] Gson全解析(上)-Gson基础
[2] Gson User Guide