Gson框架使用其实很简单,但大部分人可能只用了其中的部分功能,由于工作或功能的限制,没有太多时间去整体看看Gson到底能帮我们在数据序列化上做哪些事情,这篇文章准备根据Gson官方的API文档,同时结合项目中使用的情况做一个分享!
下面根据API 2.8.0文档里的顺序来一一解读API的内容(类名后面括号中的字符代表含义:I->interface、An->annotation、Ab->abstract、En->enum):
-
ExclusionStrategy (I)
根据名字的意思可以知道这是一个排除策略。该接口有两个函数:
boolean shouldSkipClass(Class<?> clazz)
实现该方法可以忽略类对象,在方法中制定一个排除策略,比如类名以什么为前缀\后缀的等,然后判断当前处理的对象是否符合该规则,
如果满足则返回true,则该类对象会当做Json对象的一部分被序列化/反序列化;
否则返回false,则该类对象则不会作为Json解析/输出的一部分从而不会被序列化/反序列化。
boolean shouldSkipField(FieldAttributes f)
改实现该方法和上面一个类似,只不过这个是针对对象属性的,是针对对象属性的排除策略,即该属性如果满足排序策略则会被序列化/反序列化,否则则被忽略。
Sample:
Exclude fields and objects based on a particular class type: private static class SpecificClassExclusionStrategy implements ExclusionStrategy { private final Class<?> excludedThisClass; public SpecificClassExclusionStrategy(Class<?> excludedThisClass) { this.excludedThisClass = excludedThisClass; } public boolean shouldSkipClass(Class<?> clazz) { return excludedThisClass.equals(clazz); } public boolean shouldSkipField(FieldAttributes f) { return excludedThisClass.equals(f.getDeclaredClass()); } } Excludes fields and objects based on a particular annotation: public @interface FooAnnotation { // some implementation here } // Excludes any field (or class) that is tagged with an "@FooAnnotation" private static class FooAnnotationExclusionStrategy implements ExclusionStrategy { public boolean shouldSkipClass(Class<?> clazz) { return clazz.getAnnotation(FooAnnotation.class) != null; } public boolean shouldSkipField(FieldAttributes f) { return f.getAnnotation(FooAnnotation.class) != null; } } //使用排除策略,需要使用GsonBuilder来创建Gson对象,不能使用默认的Gson创建方式: ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class); Gson gson = new GsonBuilder() .setExclusionStrategies(excludeStrings) .create(); //如果我们只想要序列化的时候使用该策略,反序列化是不使用,那么我们应该怎么写: ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class); Gson gson = new GsonBuilder() .addDeserializationExclusionStrategy(excludeStrings) .create();
是不是很简单,我们只要在两个方法中定制排除策略即可,然后创建Gson对象的时候注册进去,那么在调用gson.toJson()和gson.fromJson的时候Gson框架就会使用该策略来进行排除不想要被序列化/反序列化的类对象或属性,在实际工作中不是常用的。
-
Expose (An)
该类型是一个注解,用来标注属性是否在序列化/反序列化时可见,但是只是在属性上添加该注解是没有作用的,必须调用用 GsonBuilder.excludeFieldsWithoutExposeAnnotation() 方法才能生效。
Sample:
public class User { @Expose private String firstName; @Expose(serialize = false) private String lastName; @Expose (serialize = false, deserialize = false) private String emailAddress; private String password; }
上面一段代码是Expose在类字段上如何使用,如果不使用GsonBuilder并调用excludeFieldsWithoutExposeAnnotation()方法是没有任何作用的,即以上每个字段都会正常的被序列化/反序列化。如果调用了excludeFieldsWithoutExposeAnnotation()方法则password这个字段会被忽略,因为该字段前面没有Expose标记,在序列化过程中,被忽略的字段还有lastName和emailAddress,这是因为这两个字段中Expose的serialize属性值为false,所以会被忽略;在反序列化过程中,出了password字段还有emailAddress字段也会被忽略,因为emailAddress的Expose的deserialize属性值为false,其实如果Expose的serialize和deserialize都为false的话和不标注Expose是一样,就是忽略的意思。
其实上面的password还有一个方式可以达到被忽略的效果,那就是使用@java.beans.Transient,可以在序列化中被忽略。
-
FieldAttributes
该类型是一个最终类,不可被继承,用来存储一个属性的属性或者称之为一个字段的属性。一般使用不到这个类,这个在Gson内部框架中的源代码中可以看到它的使用。在java中,一个字段的声明包括访问修饰符,类型,注解,字段标识符。那么FieldAttributes其实就是用来保存这些信息以便在使用时调用,Java程序员可能会使用反射来获取这些信息,Gson框架中把这些封装好了,直接构造FiledAttributes对象就能得到一个字段的这些信息。它的构造函数需要一个Filed对象作为参数。该类型不常用。
-
FieldNamingPolicy (En)
该类型是一个枚举类型,用来定义Json字段名称的几个标准策略。需要和GsonBuilder配合使用。
包含以下枚举值:
IDENTITY
使用这种命名策略与Gson将确保该字段名称是不变,即默认值。
LOWER_CASE_WITH_DASHES
使用这种命名策略与Gson将修改java领域的名字从骆驼套管形式小写的字段名,每个字都是由一个破折号(-)分离。
Sample:
someFieldName ---> some-field-name
_someFieldName ---> _some-field-name
aStringField ---> a-string-field
aURL ---> a-u-r-l
LOWER_CASE_WITH_UNDERSCORES
使用这种命名策略,Gson将修改java领域的名字从骆驼套管形式小写的字段名,每个字都是用下划线分隔(_)。
Sample:
someFieldName ---> some_field_name
_someFieldName ---> _some_field_name
aStringField ---> a_string_field
aURL ---> a_u_r_l
UPPER_CAMEL_CASE
使用这种命名策略,Gson将确保首字母小写的java字段名变成大写当序列化JSON形式。
Sample:
someFieldName ---> SomeFieldName
_someFieldName ---> _SomeFieldName
UPPER_CAMEL_CASE_WITH_SPACES
使用这种命名策略,Gson将会将每个大写开头的字符串以空格的形式分开。
Sample:
someFieldName ---> Some Field Name
_someFieldName ---> _Some Field Name
-
FieldNamingStrategy
该类型是一个接口,用来处理序列化过程中对字段名自定义格式化的处理。FieldNamingPolicy就是对该接口的一个实现,通过GsonBuilder().setFieldNamingStrategy()来设置定制的名字格式化规则。
注意:经过测试,如果在字段名上已经使用了注解@SerializedName设置了别名的话,FieldNamingStrategy是无效的,而且如果同时设置了FieldNamingPolicy和自定义FieldNamingStrategy的话,按照初始化gson设置的顺序最后一个值有效,因为它们都是FieldNamingStrategy的实现,所以默认最后一个设置有效。源代码中的处理的优先级SerializedName最高。
-
Gson
这是Gson的最主要的类,Gson通常只构建一次,然后调用toJson()和fromJson()方法,并且gson对象时线程安全的,可以跨多线程使用。Gson可以直接使用new关键字来构建一个默认的gson对象,即使用所有的默认设置,但如果需要加入自定义的行为,则需要通过GsonBuilder来创建gson对象。
-
GsonBuilder
在Gson类型中以及提到了GsonBuilder,用GsonBuilder可以创建自定义配置选项的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();
注意:上面的设置顺序部分先后,仅当对同一选项设置重复值时,以最后一次设置为准。另外对于日期默认设置是不带时区信息的,需要手动设置setDateFormat()来修改配置。
-
InstanceCreator (I)
该接口用来创建反序列化实例对象,默认情况下Gson会使用无参构造函数创建一个gson对象,然后根据json的内容解析出来的值赋给创建的对应实例bean对象。但是有时候可能会有一些特殊的情况需要给bean对象添加额外的上下文信息,比如android的context对象,或者某个自定义的字段不在序列化对象中,这个值只可以在外面传递给bean对象,有人说可以等实例化好了再赋值不就行了,但有时候比如说要依赖于context对象做一些事情的话,则只能在创建bean对象时赋值,那也就是说要有一个方式能够替换gson的默认创建bean对象的方法,该接口就是这个作用。
自定义一个InstanceCreator对象,在GsonBuilder创建gson的时候通过registerTypeAdapter()注册进去即可。
Sample:
public class InstanceCreatorBean { private String name; private int num; private int append; public InstanceCreatorBean(String name, int num, int append) { this.name = name; this.num = num; this.append=append; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNum() { return num; } /*public void setNum(int num) { this.num = num; }*/ public int getAppend() { return append; } /* public void setAppend(int append) { this.append = append; }*/ }
public class TestInstanceCreator implements InstanceCreator<InstanceCreatorBean> { @Override public InstanceCreatorBean createInstance(Type type) { return new InstanceCreatorBean("jfeoafjoe", 2090,1021);//用于附加序列化额外的私有变量值,即不属于属性的成员变量例如Android的context值 } }
public void instanceCreatorTest() { InstanceCreatorBean bean = new InstanceCreatorBean("jerrite", 1000, 2000); Gson gson = new GsonBuilder().setPrettyPrinting().create(); String json = gson.toJson(bean); Log.d(TAG, "instanceCreatorTest:" + json); String reJson = "{" + "\"name\": \"jerrite2\"," + " \"num\": 1000" + "}"; Gson gson2 = new GsonBuilder()//.registerTypeAdapter(InstanceCreatorBean.class, new TestInstanceCreator()) .setPrettyPrinting() .create(); InstanceCreatorBean bean2 = gson2.fromJson(reJson, InstanceCreatorBean.class); if (bean2 != null) { Log.d(TAG, "instanceCreatorTest name:" + bean2.getName() + " num:" + bean2.getNum() + " append:" + bean2.getAppend()); } }
注意:上面示例中创建的对象传进去的参数,如果在json对象中有对应的字段信息,则会在解析json数据时被替换掉。另外,Gson对默认的bean对象的私有字段也是可以序列化和反序列化的。
-
JsonAdapter
该类型是一个注解,用来给类或字段注释自定义TypeAdatper对象,和通过GsonBuilder注册的效果一样,通过注解的方式可以方便开发者简化代码,以及提高可读性,并且是和bean对象时紧密相连的,具有特指性质。
Sample:
@JsonAdapter(UserJsonAdapter.class) public class User { public final String firstName, lastName; private User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } public class UserJsonAdapter extends TypeAdapter<User> { @Override public void write(JsonWriter out, User user) throws IOException { // implement write: combine firstName and lastName into name out.beginObject(); out.name("name"); out.value(user.firstName + " " + user.lastName); out.endObject(); // implement the write method } @Override public User read(JsonReader in) throws IOException { // implement read: split name into firstName and lastName in.beginObject(); in.nextName(); String[] nameParts = in.nextString().split(" "); in.endObject(); return new User(nameParts[0], nameParts[1]); } }
下面这个示例用于字段:
注意:可以给一个字段添加多个类型适配器,但是字段注解的优先级高于GsonBuilder注册的,而GsonBuildr注册的注解又高于类型注解设置的类型适配器。这个虽然没有测试,根据官网翻译过来的,有待验证,不过这些都不是重要的,只需要知道就行了。还有一个要注意的是注解设置的值必须是 TypeAdapter 或 TypeAdapterFactory的实现对象。.private static final class Gadget { @JsonAdapter(UserJsonAdapter2.class) final User user; Gadget(User user) { this.user = user; } }
-
JsonArray
这是Json的数组对象,用于手动处理数组中的每个jsonElement,当一个数组中的类型不一致时,JsonArray就可以用来手动的处理数据,JsonArray是一个有序数组,即item添加的顺序。一般情况下我们不需要用到这个对象,像List<T> 泛型 都是可以通过TypeToken直接处理的。使用JsonArray也很简单,就像遍历ArrayList一样一层层的遍历,然后通过JsonObject获取每个字段值。
-
JsonDeserializationContext
Gson上下文对象,在自定义JsonDeserializer的时候,在实现方法中会有这个对象,其实这个就是Gson对象,在自定义JsonDeserializer的deserialize的时候如果需要用到Gson上下文对象对其内部的某个字段类型继续反序列化,而不需要再创建一个Gson对象。
比如:
class A{ private int property; private B b; } class B{ }
上面两个类,当对A对象反序列化是,如果要对property做统一修改,那么我们需要自定义反序列化,但是在Gson创建的时候已经注册了B对象的类型适配器,这时我们不要再创建一个Gson对象类解析B对象,而是直接使用传进来的上下文JsonDeserializationContext对象直接序列化即可。
注意:传进来的JsonDeserializationContext不可以序列化当前作为Type传进来的对象,怎么理解呢?有一种情况 A对象有好多个属性,我们只要对其中一个字段做统一处理,其它的使用默认方式解析即可,但是自定义JsonDeserializer后就必须对每个字段手动的解析,那有人想就先用JsonDeserializationContext来对A做处理,然后在修改那个属性不就可以了? 这样做就会进入一个递归操作,因为Gson在序列化或反序列化时回去配置里寻找对应的自定义JsonDeserializer,如果找到了就会使用自定义的对象,这样就会形成递归,不停的自己调用自己。所以JsonDeserializationContext除了当前解析的类型对象不能重复利用,该类型对象的字段类型则都可以复用JsonDeserializationContext对象。
-
JsonDeserializer (I)
该接口就是当要自定义反序列化器的时用到的j接口,在JsonDeserializationContext已经介绍过了,这里不多做介绍,当自定义一个反序列化器的时候,只要实现对应的方法,然后在GsonBuilder中注册即可。
-
JsonElement (Ab)
这是一个抽象类,是 JsonObject, a JsonArray, a JsonPrimitive or a JsonNull的父类。其中封装了好多方法,通过对应的方法可以将该对象转换为具体的基本类型,或它的子类型。
-
JsonIOException
这个就不用说了,gson框架的异常类,当序列化或反序列化过程中IO异常时就可以用该类来获取详细的异常信息。
-
JsonNull
和NULL对象对应,在Json序列化或反序列化过程中,当遇到空对象时,就可以用该对象来表示。
-
JsonObject
Json对象,对象包含key-value的键值对值,key是String型的值,value是其它的JsonElement对象。JsonObject对象中的成员顺序是按照被添加的顺序保存在对象中的。该对象封装了好多方法,包括添加字段,转换对应的JsonElement对象等。查看API一目了然,这里不做太多解释。JsonObject其实就是一个具体的Json对象,可以使最外层的对象也可以是某个字段对象。
-
JsonParseException
略过。
-
JsonParser
将Json解析成JsonElements的解析树,然后就可以根据JsonElement一层一层的获取每个JsonElement的值了。说白了就是讲Json字面值的格式 转换成JsonElemnt对象。在手动解析Json对象时,我们需要先将Json字符串或Json流转换成JsonElement对象,然后在转换成JsonObject或JsonArray等具体的类型,然后再一层一层的解析,最终得到每个对象每个字段的值。
-
JsonPrimitive
该类型其实是对基础类型的封装,可以转换为int、string、long、float等或者java原始封装的类型。其实就是Gson定义的一个基本类型转换器或者类型封装器,用来将Java的基本类型描述成Json的类型。
-
JsonReader
这个类算是Gson解析Json对象的关键类了,fromJson()其实最终解析的对象就是这个对象,包括JsonParser它的内部实现也是基于JsonReader。
该版本的JsonReader对象是基于RFC7159的标准读取解析Json的令牌的即标识符(是一个大括号的对象 还是方括号的数组),JsonReader会将Json对象读取到一个Stream中,该Stream中包含了 (strings, numbers, booleans, and nulls)的字面值,同时包含了json对象和json数组的分隔符。JsonReader是按照深度优先的顺序读取令牌的。
JsonReader是如何解析JsonObject的:
首先调用beginObject()方法标识开始解析一个单个对象,并且消耗(读取)一个大括号,然后循环的根据字段名获取对应的值,当hasNext()返回false时,循环结束,当调用endObject()方法代表读取object结束。
JsonReader是如何解析JsonArray的:
首先调用beginArray()方法标识开始解析一个数组对象,然后循环分别读里面的每个object,当hasNext()返回false时,循环结束,当调用endObject()方法代表读取array结束。
Sample:
Json字面值:
[ { "id": 912345678901, "text": "How do I read a JSON stream in Java?", "geo": null, "user": { "name": "json_newb", "followers_count": 41 } }, { "id": 912345678902, "text": "@json_newb just use JsonReader!", "geo": [50.454722, -104.606667], "user": { "name": "jesse", "followers_count": 2 } } ]
解析步骤:
注意:在处理数值型Json对象时,JsonReader可以将numeric的值作为String值读取,也可以将String的值作为numeric的值读取。比如JsonArray[1,"1"],里面包含了一个整型值1和字符串整数值“1”,为了防止数值转换精确度丢失,可以使用nextInt()和nextString()来读取。对于特别大的数值使用字符串保存比如double是javascript中的唯一数值类型,当一个数字特别大操作了double类型可表示的值时,那么数值的精度就会丢失,从而使得计算不准确。public List<Message> readJsonStream(InputStream in) throws IOException { JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); try { return readMessagesArray(reader); } finally { reader.close(); } } public List<Message> readMessagesArray(JsonReader reader) throws IOException { List<Message> messages = new ArrayList<Message>(); reader.beginArray(); while (reader.hasNext()) { messages.add(readMessage(reader)); } reader.endArray(); return messages; } public Message readMessage(JsonReader reader) throws IOException { long id = -1; String text = null; User user = null; List<Double> geo = null; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if (name.equals("id")) { id = reader.nextLong(); } else if (name.equals("text")) { text = reader.nextString(); } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) { geo = readDoublesArray(reader); } else if (name.equals("user")) { user = readUser(reader); } else { reader.skipValue(); } } reader.endObject(); return new Message(id, text, user, geo); } public List<Double> readDoublesArray(JsonReader reader) throws IOException { List<Double> doubles = new ArrayList<Double>(); reader.beginArray(); while (reader.hasNext()) { doubles.add(reader.nextDouble()); } reader.endArray(); return doubles; } public User readUser(JsonReader reader) throws IOException { String username = null; int followersCount = -1; reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if (name.equals("name")) { username = reader.nextString(); } else if (name.equals("followers_count")) { followersCount = reader.nextInt(); } else { reader.skipValue(); } } reader.endObject(); return new User(username, followersCount); }
对于Json格式的完整性,有时候web服务器可能遭到黑客攻击,在Json数据中添加<script>这样的HTML标签,此时将Json的前缀改为")]}'\n" ,从而使得<script>不可执行,从而防止了js攻击。但是Json这样修改后结构就不是正确的Json格式,从而导致解析出错了,不过JsonReader能够兼容这种不可执行的前缀,只要设置setLenient(true)即可。另外一个JsonReader对象时非线程安全的,每个JsonReader对象可能用于读取单个json stream。
-
JsonSerializationContext
参照JsonDeserializationContext的使用。
-
JsonSerializer
参照JsonDeserializer的使用。
-
JsonStreamParser
改parser可以同时解析多个JsonElement,之前的JsonParser只能解析一个JsonElement对象,即Json字面值是以一个大括号开始的。而这个Parser可以同时解析多个JsonElement,可以使JsonArray、JsonObject等JsonElement的子类的混合形式,比如: "['json 1 first'] {'json 1 second':10} 'json 1 third'";这个就是多对象混合的例子,由于服务器端会根据某些特殊的业务需求,如果按照正常的Json规范去解析 这可能需要定义一个超级类去包含多个对象,假如定了三个实体类对象 A 、B和C,如果要一次把这三个对象返回给调用者,那么一般做法会再定义一个类 然后把这三个类作为三个字段然后在封装成Json传输,根据面向对象思想,这个额外定义的类是没有任何意义的,只是为了提供这么一个接口而定义,那么这时用JsonStreamParser就可以不需要额外定义多余的类对象,只要将三个对象值按照某个顺序序列化为一个Json流数据,然后调用者按照这个顺序去分别解析即可。
JsonStreamParser根据官方的解释是线程安全的,但是是有条件的,具体什么意思没理解,不过根据demo的测试总结如下:如果跨线程去处理Json数据,JsonStreamParser解析之前需要做同步处理,否则会报错,下面是两次实验得出的错误信息:
Error1:java.lang.IllegalStateException: Expected a string but was END_ARRAY
Error2:com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated array
明显这两个信息不一样,为什么会这样?其实当parser.next()被调用的时候,parser会去解析Json内容,之所以叫StreamParser,它的数据其实也是基于StreamReader读取的,那么next()调用一次就会从缓存中取出一部分数据,如果两个线程同时操作next()方法,那么就是两个线程同时读取一个缓存中的数据,导致最后格式不正确,所以要在读取之前加同步锁!
Sample:
private class StreamParserRunable implements Runnable { private JsonStreamParser parser; public StreamParserRunable(JsonStreamParser parser) { this.parser = parser; } @Override public void run() { JsonElement element; while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //synchronized (parser) { if(!parser.hasNext()){ break; } element = parser.next(); if (element.isJsonArray()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonArray." + element.getAsString()); } else if (element.isJsonObject()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonObject." + element.getAsJsonObject().get("json 1 second")); } else if (element.isJsonNull()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonNull."); } else if (element.isJsonPrimitive()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonPrimitive." + element.getAsString()); } //} } } } private class StreamParserRunable2 implements Runnable { private JsonStreamParser parser; public StreamParserRunable2(JsonStreamParser parser) { this.parser = parser; } @Override public void run() { JsonElement element; while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //synchronized (parser) { if(!parser.hasNext()){ break; } element = parser.next(); if (element.isJsonArray()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonArray." + element.getAsString()); } else if (element.isJsonObject()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonObject." + element.getAsJsonObject().get("json 1 second")); } else if (element.isJsonNull()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonNull."); } else if (element.isJsonPrimitive()) { Log.d(TAG, Thread.currentThread().getName() + "---element is jsonPrimitive." + element.getAsString()); } } //} } }
写个demo测试便可知其中原理,无序分析源代码。至于conditionally thread-safe是什么意思,可以看看Thread_safety里面描述的比较详细。public void testMultipleThreads() { String json1 = "['json 1 first'] {'json 1 second':10} 'json 1 third'"; String json3 = "['json 2 first'] {'json 2 second':10} 'json 2 third'"; JsonStreamParser streamParser = new JsonStreamParser(json1); cachedThreadPool.execute(new StreamParserRunable(streamParser)); cachedThreadPool.execute(new StreamParserRunable2(streamParser)); }
-
JsonSyntaxException
Json语法错误异常,略过。
-
JsonToken
该枚举类型是对Json所有标识符的描述,比如JsonToken BEGIN_ARRAY和JsonToken END_ARRAY代表JsonArray对象的开始和结束标记,目的就是为了在整个框架中对标识符的统一描述,比使用常量值“[ ]”语义更明确。在调用某些方法写入数据或解析某个JsonElement时判断标识符是什么时会用到。
-
JsonWriter
和JsonReader对应,通过JsonWriter可以构造一个Json对象。
Sample:
[
{
"id": 912345678901,
"text": "How do I stream JSON in Java?",
"geo": null,
"user": {
"name": "json_newb",
"followers_count": 41
}
},
{
"id": 912345678902,
"text": "@json_newb just use JsonWriter!",
"geo": [50.454722, -104.606667],
"user": {
"name": "jesse",
"followers_count": 2
}
}
]
public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException { JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8")); writer.setIndent(" "); writeMessagesArray(writer, messages); writer.close(); } public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException { writer.beginArray(); for (Message message : messages) { writeMessage(writer, message); } writer.endArray(); } public void writeMessage(JsonWriter writer, Message message) throws IOException { writer.beginObject(); writer.name("id").value(message.getId()); writer.name("text").value(message.getText()); if (message.getGeo() != null) { writer.name("geo"); writeDoublesArray(writer, message.getGeo()); } else { writer.name("geo").nullValue(); } writer.name("user"); writeUser(writer, message.getUser()); writer.endObject(); } public void writeUser(JsonWriter writer, User user) throws IOException { writer.beginObject(); writer.name("name").value(user.getName()); writer.name("followers_count").value(user.getFollowersCount()); writer.endObject(); } public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException { writer.beginArray(); for (Double value : doubles) { writer.value(value); } writer.endArray(); }
注意:JsonWriter是线程安全的,需要再单独的线程用使用。
-
LongSerializationPolicy
该枚举类型是用来定义long型值的默认格式化方式,在上面也提到了,在某些平台或语言中,不支持long型在转换过程中不够精确,所以可以保存为String类型。该枚举有两个值:
DEFAULT
默认输出long型对象;
STRING
输出为String类型对象。
-
MalformedJsonException
当解析一个错误格式的Json对象时会报此异常。但是有些语法错误可以通过JsonReader.setLenient(boolean).来忽略。在上面的防止Html中javascript攻击内容中已经介绍过了。
-
SerializedName
这是一个注解,用来标识字段序列化和反序列化时的字段名字。在名字策略中已经介绍过了。
-
Since
这个注解用来标记某个字段在某个版本中是否被序列化或反序列化。 Since表示在Since的value值的版本以及以后的版本中该字段存在,也就表示可以被序列化和反序列化。Since的value值的比较对象是在GsonBuilder.setVersion(version)设置的值。它的作用就是在发布版本时,有可能某些字段是在某个版本后才有的,因此老版本中需要对其忽略过滤。
Sample:
public class User { private String firstName; private String lastName; @Since(1.0) private String emailAddress; @Since(1.0) private String password; @Since(1.1) private Address address; }
-
TypeAdapter
这是Gson框架中一个核心类之一。用来自定义json类型适配器,通过GsonBuilder注册到gson中,以后通过toJson()/fromJson()时Gson就会去配置里去找到对应应的类型适配器,如果存在则选用,否则使用默认的类型适配器。上面既然有了自定义JsonSerializer和JsonDeserializer用来处理特殊的解析需求,TypeAdapter又有什么优势呢?其实JsonSerializer和JsonDeserializer最终也是被封装成了 TreeTypeAdapter对象,然后放到了adapter集合里,官方在最下面写了一句“ New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface's tree API.”意思就是自定义解析方式选用TypeAdapter比JsonSerializer和JsonDeserializer更高效,具体可以看源代码里是怎么实现的。
Sample:
public class PointAdapter extends TypeAdapter<Point> { public Point read(JsonReader reader) throws IOException { if (reader.peek() == JsonToken.NULL) { reader.nextNull(); return null; } String xy = reader.nextString(); String[] parts = xy.split(","); int x = Integer.parseInt(parts[0]); int y = Integer.parseInt(parts[1]); return new Point(x, y); } public void write(JsonWriter writer, Point value) throws IOException { if (value == null) { writer.nullValue(); return; } String xy = value.getX() + "," + value.getY(); writer.value(xy); } } GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(Point.class, new PointAdapter()); // if PointAdapter didn't check for nulls in its read/write methods, you should instead use // builder.registerTypeAdapter(Point.class, new PointAdapter().nullSafe()); ... Gson gson = builder.create();
如果使用默认的类型适配器,那么point的json字面值应该是: {"x":5,"y":8}, 上面的adapter则可以解析一个"5,8"这样字符串为point对象,活着序列化为"5,8"这样的字符串。这样就能更加灵活的处理接口提供的数据。这有点类似于重载运算符一样。
-
TypeAdapterFactory
上面有个TypeAdapter,那这个TypeAdapterFactory有时干嘛用的?TypeAdapter只针对一个具体的类型定制,那如果有几个类似的结构是不是要每个都定义一个TypeAdapter呢?当然不是,定义TypeAdapterFactory则可以解决这一问题,TypeAdapterFactory用来处理一类类型的自定义适配问题,现在都是面向对象思想了,所以应该不难理解。
Sample:
public class LowercaseEnumTypeAdapterFactory implements TypeAdapterFactory { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { Class<T> rawType = (Class<T>) type.getRawType(); if (!rawType.isEnum()) { return null; } final Map<String, T> lowercaseToConstant = new HashMap<String, T>(); for (T constant : rawType.getEnumConstants()) { lowercaseToConstant.put(toLowercase(constant), constant); } return new TypeAdapter<T>() { public void write(JsonWriter out, T value) throws IOException { if (value == null) { out.nullValue(); } else { out.value(toLowercase(value)); } } public T read(JsonReader reader) throws IOException { if (reader.peek() == JsonToken.NULL) { reader.nextNull(); return null; } else { return lowercaseToConstant.get(reader.nextString()); } } }; } private String toLowercase(Object o) { return o.toString().toLowerCase(Locale.US); } }
GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory()); ... Gson gson = builder.create();
public class MultisetTypeAdapterFactory implements TypeAdapterFactory { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { Type type = typeToken.getType(); if (typeToken.getRawType() != Multiset.class || !(type instanceof ParameterizedType)) { return null; } Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType)); return (TypeAdapter<T>) newMultisetAdapter(elementAdapter); } private <E> TypeAdapter<Multiset<E>> newMultisetAdapter( final TypeAdapter<E> elementAdapter) { return new TypeAdapter<Multiset<E>>() { public void write(JsonWriter out, Multiset<E> value) throws IOException { if (value == null) { out.nullValue(); return; } out.beginArray(); for (Multiset.Entry<E> entry : value.entrySet()) { out.value(entry.getCount()); elementAdapter.write(out, entry.getElement()); } out.endArray(); } public Multiset<E> read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } Multiset<E> result = LinkedHashMultiset.create(); in.beginArray(); while (in.hasNext()) { int count = in.nextInt(); E element = elementAdapter.read(in); result.add(element, count); } in.endArray(); return result; } }; } }
上面两个例子是官方给出的,基本看完例子也就能明白这个TypeAdapterFactory怎么用了。有人会问了,这个TypeAdapterFactory只在create里面对类型进行区分,注册的时候并没有与具体的类型关联,那Gson是如何适配的呢?如果非要纠结具体的实现那就看源代码吧,下面把关键的源代码贴出来:
值得注意的一点,注册TypeAdapter和前面提到的注册排除策略不同,排除策略对同一类型注册多个策略时以最后一个为准,其实内部是使用map保存的,同一个key不能有重复的值,而TypeAdapter则相反,是最先注册的会被使用。所以上面的代码会先到缓存中获取如果没有继续到当前threadLocal对象中获取,如果还没有则遍历所有的factories,所以会在这个方法里对注册的所有typeadapter进行筛选,最先注册的优先。public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) { TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type); if (cached != null) { return (TypeAdapter<T>) cached; } Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get(); boolean requiresThreadLocalCleanup = false; if (threadCalls == null) { threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>(); calls.set(threadCalls); requiresThreadLocalCleanup = true; } // the key and value type parameters always agree FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type); if (ongoingCall != null) { return ongoingCall; } try { FutureTypeAdapter<T> call = new FutureTypeAdapter<T>(); threadCalls.put(type, call); for (TypeAdapterFactory factory : factories) { TypeAdapter<T> candidate = factory.create(this, type); if (candidate != null) { call.setDelegate(candidate); typeTokenCache.put(type, candidate); return candidate; } } throw new IllegalArgumentException("GSON cannot handle " + type); } finally { threadCalls.remove(type); if (requiresThreadLocalCleanup) { calls.remove(); } } }
再看一段代码即可明白所有的TypeAdapter以及JsonSerializer和 JsonDeserializer最后都是封装成了TypeAdapterFactory对象:
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) { $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?> || typeAdapter instanceof InstanceCreator<?> || typeAdapter instanceof TypeAdapter<?>); if (typeAdapter instanceof InstanceCreator<?>) { instanceCreators.put(type, (InstanceCreator) typeAdapter); } if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) { TypeToken<?> typeToken = TypeToken.get(type); factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter)); } if (typeAdapter instanceof TypeAdapter<?>) { factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter)); } return this; }
这个可能比较复杂,需要看源代码了解原理,但正常使用不需要了解和么多,只要实现对应的方法就行了。
-
TypeToken
这个是Gson对java Class的类型元数据的封装,比如类型名,类型完整名,泛型类型等等。具体查看api即可。
-
Until
这个注解和Since的作用类似,只不过是Since表示自某个版本后该字段可以被识别,而Until则表示被标注的字段在某个版本之前可以别识别。因为在版本更新过程中有可能对某些代码重构,有可能有些字段被弃用了,所以在版本切换过程中这两个注解是很有用的。
Smaple:
public class User { private String firstName; private String lastName; @Until(1.1) private String emailAddress; @Until(1.1) private String password; }
-
部分原理解析