现在轮到TypeAdapter类上场,但考虑到gson默认行为已足够强大,加上项目实践中应用json时场景不会太复杂,所以一般不需要自定义TypeAdapter。TypeAdapter优点是集成了JsonWriter和JsonReader两个类,定义了一套与gson框架交互的良好接口,同时便于管理编码和解码的实现代码,不至于太零碎。因而在了解JsonReader和JsonWriter的使用方法之后,自定义TypeAdapter类来完成特定类的编码和解码也就不困难了。
Type listType = new TypeToken<List<String>>() {}.getType();//!!! List<String> target = new LinkedList<String>(); target.add("blah"); Gson gson = new Gson(); String json = gson.toJson(target, listType); List<String> target2 = gson.fromJson(json, listType);// !!!
分析TypeToken类的实现代码,发现GSON的开发者想出了一个比较有意思的实现方法,既然不能直接通过类型本身得到真实的类型对象,那么就从对象本身得到。而TypeToken就是这一想法的通用实现。TypeToken类本身不能直接实例化,使用时需要定义其子类对象,这时即通过子类对象来获取其模板类型参数,也就得到了泛型类型的真实类型。
比如面对类似 [{"Qualified Name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}},{"Qualified Name":"Jackie Boy","age":1,"contact":{"email":"email","phoneno":"phoneno"}}] 的json格式字符串,对于程序来说理解这样的字符串毫无压力,但对于人来说困难就比较大了。那怎么办呢?gson有办法,通过修改gson的默认行为,可以在输出成json字符串时,提供缩进使用字符串表现出良好的格式。样例代码和缩进后的输出如下。
final Gson gson = new GsonBuilder().setVersion(1.1).setPrettyPrinting().create();// 调用setPrettyPrinting方法,改变gson对象的默认行为 如下即是输出 [ { "Qualified Name": "Jackie", "age": 30, "contact": { "email": "email", "phoneno": "phoneno" } }, { "Qualified Name": "Jackie Boy", "age": 1, "contact": { "email": "email", "phoneno": "phoneno" } } ]
这个特性很有意思,但项目实际开发的时候可能意义不大,毕竟换行、缩进都是占用空间的。日志里输出json格式的字符串时,倒是可以考虑写入调整过格式后的json字符串,在事后分析日志时方便阅读,给运维同事减轻负担。
基于已有的使用经验,如果不做对字段进行特别的处理,对于引用类型的成员,如果取值为null时,编码后将不会出现在json字符串中,这是gson的默认行为。但通过修改gson的默认行为,可以将取值为null的字段也输出到json字符串中。样例代码如下,为了方便查看,还设置了缩进。
final Gson gson = new GsonBuilder().setVersion(1.1).serializeNulls().setPrettyPrinting().create();// 调用serializeNulls方法,改变gson对象的默认行为 如下是输出 [ { "Qualified Name": "Jackie", "age": 30, "contact": { "email": "email", "phoneno": "phoneno" }, "address": null,// address和location被原样输出 "location": null }, { "Qualified Name": "Jackie Boy", "age": 1, "contact": { "email": "email", "phoneno": "phoneno" }, "address": null, "location": null } ] // 如下是类定义 @Data class Person { @Since(1.0) @SerializedName("Qualified Name") private String name; @Since(1.1) private int age; @Since(1.0) private Map<String, Object> contact; private String address; private String location; public Person() { contact = new HashMap<String, Object>(); } public void putValue(final String key, final Object value) { contact.put(key, value); } }
这是一个好问题。有这么几种可能:
设计如下的样例代码,可以检验一下刚才的猜测。
import java.util.HashMap; import java.util.Map; import lombok.AccessLevel; import lombok.Data; import lombok.Getter; import com.google.gson.Gson; 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 String json = gson.toJson(jack1); System.out.println(json); final Gson gson2 = new Gson(); final Person p = gson2.fromJson(json, Person.class); System.out.println(p); } } @Data class Person { private String name; @Getter(AccessLevel.NONE) // @Setter(AccessLevel.NONE) private int age; private Map<String, Object> contact; private String address; private String location; public Person() { age = 20; contact = new HashMap<String, Object>(); } public void putValue(final String key, final Object value) { contact.put(key, value); } /* public int getAGe() { return age; } public int getAge() { return 90; } */ }样例代码的输出如下。
{"name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}} Person(name=Jackie, age=30, contact={email=email, phoneno=phoneno}, address=null, location=null)
前述代码主要是在验证成员的公有方法对gson为对象生成json字符串时的影响。比如通过使用注解,控制lombok不为Person类的成员age生成getter方法,或者写一个命名不合要求的访问方法,或者写一个命名符合要求,但是取值与成员age的值无关的公有getter方法。经验证,这几种修改方法对最终的结果没有任何影响,因而从上面的样例代码可以得出结论,gson在为Bean对象生成json时,并没有依赖类定义中的公有方法。假如gson直接使用Bean元数据中的成员信息来直接生成json的话,暂时没有想到怎样设计样例代码来检验,所以只好单步跟踪gson在生成json时的代码。最终跟踪到了如下的一段代码。
// 如下代码来自于gson 2.2.4,版权归作者所有 Field[] fields = raw.getDeclaredFields(); for (Field field : fields) { boolean serialize = excludeField(field, true); boolean deserialize = excludeField(field, false); if (!serialize && !deserialize) { continue; } field.setAccessible(true); Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); BoundField boundField = createBoundField(context, field, getFieldName(field), TypeToken.get(fieldType), serialize, deserialize); BoundField previous = result.put(boundField.name, boundField); if (previous != null) { throw new IllegalArgumentException(declaredType + " declares multiple JSON fields named " + previous.name); } }这段代码在类com.google.gson.internal.bind.ReflectiveTypeAdapterFactory的方法getBoundFields中,不相关的部分没有列出来。从这段代码可以看出gson直接读取JavaBean的成员信息来完成json的生成,并没有校验成员是否定义了get/set方法。
GSON使用的学习笔记,进阶篇(三),布布扣,bubuko.com
原文地址:http://blog.csdn.net/jackie_xiaonan/article/details/16853215