自定义解析过程
使用JsonReader
依靠gson提供的类JsonReader,用如下一段样例程序来说明如何解决当前的难题。
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
public class JsonTest {
public static void main(final String[] args) {
final Gson gson = new Gson();
final Person jack1 = new Person();
jack1.setAge(30);
jack1.setName("Jackie");
jack1.putValue("email", "email");
jack1.putValue("phoneno", "phoneno");
final Person jack2 = new Person();
jack2.setAge(1);
jack2.setName("Jackie Boy");
jack2.putValue("email", "email");
jack2.putValue("phoneno", "phoneno");
final String json = gson.toJson(Arrays.asList(jack1, jack2));
System.out.println(json);
List<Person> jacks = null;
// 自定义解码处理过程,跳过对字段age的处理
JsonReader reader = null;
InputStream in = null;
try {
in = new ByteArrayInputStream(json.getBytes("UTF-8"));
reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
jacks = decode(reader);
for (final Person person : jacks) {
System.out.println("jack = " + person);
}
}
catch (final Exception e) {
e.printStackTrace();
}
finally {
if (in != null) {
try {
in.close();
}
catch (final IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
}
catch (final IOException e) {
e.printStackTrace();
}
}
}
}
// 如下方法是重点,解析操作的处理过程
private static List<Person> decode(JsonReader reader) throws IOException {
List<Person> jacks;
jacks = new ArrayList<Person>();
reader.beginArray();// 通知gson框架,这里开始解析的是数组类型
while (reader.hasNext()) {
final Person p = new Person();
jacks.add(p);
reader.beginObject();// 通知gson框架,这里开始解析的是对象
while (reader.hasNext()) {
final String name = reader.nextName();// 提到名、值对中的名
if (name.equals("name")) {
final String value = reader.nextString();
p.setName(value);
}
else if (name.equals("contact")) {
reader.beginObject();// 通知gson框架,这里开始解析的是对象
while (reader.hasNext()) {
final String key = reader.nextName();
final String value = reader.nextString();
p.putValue(key, value);
}
reader.endObject();// 通知gson框架,对对象的解析完成
}
else {
reader.skipValue();// 跳过不必要的字段,根据之前的设定,这里只能是age
}
}
reader.endObject();// 通知gson框架,对对象的解析完成
}
reader.endArray();// 通知gson框架,数组对象的解析完成
return jacks;
}
}
@Data
class Person {
private String name;
private int age;
private Map<String, Object> contact;
public Person() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
上述代码执行后的输出如下。从输出可以看出,age字段没有赋值,说明上述代码功能正确。[{"name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}},{"name":"Jackie Boy","age":1,"contact":{"email":"email","phoneno":"phoneno"}}]
jack = Person(name=Jackie, age=0, contact={email=email, phoneno=phoneno})
jack = Person(name=Jackie Boy, age=0, contact={email=email, phoneno=phoneno})
问题虽然得到了解决,但代价有点高,代码有点多。上述代码用来练练手、学习一下gson的基本原理还行,但如果在项目里也这么应用,一方面太复杂,另一方面gson的能力也没发挥出来。假如缩小问题范围,增加一些约束,比如在定义Java Bean时成员名称和待解析的json串中的字段名相同,并且各字段的值在解析时不需要做复杂的处理,那么依赖强大的gson,上述代码可以做一些简化。
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
public class JsonTest {
public static void main(final String[] args) {
final Gson gson = new Gson();
final Person jack1 = new Person();
jack1.setAge(30);
jack1.setName("Jackie");
jack1.putValue("email", "email");
jack1.putValue("phoneno", "phoneno");
final Person jack2 = new Person();
jack2.setAge(1);
jack2.setName("Jackie Boy");
jack2.putValue("email", "email");
jack2.putValue("phoneno", "phoneno");
final String json = gson.toJson(new Person[] { jack1, jack2 });
System.out.println(json);
final Person2[] persons = gson.fromJson(json, Person2[].class);
for (final Person2 p : persons) {
System.out.println(p);
}
}
}
@Data
class Person {
private String name;
private int age;
private Map<String, Object> contact;
public Person() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
// 删除age成员
@Data
class Person2 {
private String name;
private Map<String, Object> contact;
public Person2() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
依赖gson的能力,可以定义一个相同的Java Bean,同时忽略掉不必要的字段age,使用常规的方法即可以完成解析操作。不爽之处在于需要定义一个新的Java Bean。使用注解
gson的注解Expose是一个有趣的特性,可以在上前述简化方案的基础上少定义一个Java Bean类,但也引入了新的约束,如下是完整的样例。
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
public class JsonTest {
public static void main(final String[] args) {
// final Gson gson = new Gson();
final Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();// 表示通过注解来控制json的解析和生成
final Person jack1 = new Person();
jack1.setAge(30);
jack1.setName("Jackie");
jack1.putValue("email", "email");
jack1.putValue("phoneno", "phoneno");
final Person jack2 = new Person();
jack2.setAge(1);
jack2.setName("Jackie Boy");
jack2.putValue("email", "email");
jack2.putValue("phoneno", "phoneno");
final String json = gson.toJson(new Person[] { jack1, jack2 });
System.out.println(json);
final Person[] persons = gson.fromJson(json, Person[].class);
for (final Person p : persons) {
System.out.println(p);
}
}
}
@Data
class Person {
@Expose
private String name;
@Expose(serialize = true, deserialize = false)// 表示在生成json字符串时输出age成员,但由字符串解析得到对象时,不去获取age字段的值
private int age;// 对于没有使用@Expose标记的字段,则不会在json字符串中出现,同样也不会被解析
@Expose
private Map<String, Object> contact;
public Person() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
===================
我本人接触json比较晚,一直以来做的都是C/S架构的产品,如果这次不是项目需要,肯定不会闲来无事想起来研究用GSON来解析json字符串。从我本人的经历看,json可能的使用场景有如下几种:
1、向页面传递数据,需要把数据对象编码成json格式,方便页面使用。页面在收到json格式的数据之后,只需要一行代码即可以转换为javascript对象,然后即可按照格式提取数据,非常方便。如果向页面传递的数据结构不是很复杂,那么不见得要使用gson,手写代码一样可以胜任,缺点是有一定的侵入性,json格式变更或者数据对象有变更时,编码相关的方法肯定需要修改。另外手写编码器的过程比较枯燥,创造性的工作反而成了苦力,这点令人不爽,重复而简单的工作应当交给机器来做。使用gson来进行编码的话,一般只需要修改Java Bean的定义,其它烦恼事都交给gson做,因而基本没有什么工作量,相比较而言省事、方便一些。
2、页面向后台传递数据,页面把数据编码成了json格式,后台完成相关的数据处理,这时需要把数据从json串里提取出来。理论上讲,页面向服务端传递的数据一般来源于用户的键盘输入,规模不会太大,格式也比较简单,所以手写解码代码基本可以解决相当一部分问题。
3、部件之间通信时使用json格式做为消息编码方式,这样收和发消息的部件都需要实现json编码、解码的操作。从我个人的经验看,部件之间的通信格式是程序员或者设计师设计的,从减少自身工作量的角度考虑,数据的格式都不会太复杂,但部件之间的通信接口比较多,使用gson可以极大的提升开发效率,快速完成特性的开发。
===================
项目里遇到的场景比较奇葩,待解析的json格式的字符串中还存在一些不必要的字段,但是提供数据的部件为了实现简单,以及所谓的数据完整性,并没有把我当前不关心的数据过滤掉;而我需要的数据格式和对方提供的格式存在较大的差异,这样就不能通过直接对照结构定义Java Bean来直接完成解析操作。现实问题摆在眼前,必须对json解析的过程进行某种控制,使其生成我需要的数据,同时忽略不关心的字段。但是gson很强大,控制解析过程的想法完全是多余的,只要在Java Bean的定义中忽略不必要的字段,然后把类型传递给gson对象来解析输入的字符串,直接就可以得到对应的Java
Bean,不需要做特别的处理。