Gson 源码分析 总结

1   Gson 的简单使用:

简单对象的序列化和反序列化:
Gson gson = new Gson(); // Or use new GsonBuilder().create();
MyType target = new MyType();
String json = gson.toJson(target); // serializes target to Json
MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
集合类型的序列化和反序列化:
Type listType = new TypeToken>() {}.getType();
List target = new LinkedList();
target.add("blah");
Gson gson = new Gson();
String json = gson.toJson(target, listType);
List target2 = gson.fromJson(json, listType);


2   Gson 的源码解析:
 
2.1  Gson 的总体结构
Gson 源码分析 总结_第1张图片

Gson的类结构很简单,共约12个类,8个接口,2个枚举。其中主要和外界打交道的类就com.google.gson.Gson和com.google.gson.GsonBuilder这两个门面类,这两个类都提供了构造,序列化和反序列化等解析器所需要的几乎所有功能。
GsonGsonBuilder他们的具体区别如下:
    -  通过Gson()构造的解析器,调用 toJson()方法生成的json字符串中删除了所有不必要的空白。可以通过设置GsonBuilder.setPrettyPrinting().来改变这规则。
    -  生成的JSON省略了所有为空的字段。注意在数组作为null,因为数组是有序列表。但是,如果一个字段没有空,但其生成JSON为空,这个字段是保留的。可以通过设置GsonBuilder.serializeNulls().来使  Gson系列化空的字段。
    -  Gson 提供了默认的序列化和反序列化的基本类型,如Enums, MapURLURILocaleDateBigDecimal,  BigInteger classes等,这些基本上和Java中的类型是一样的,如果想改变默认的表现形式,可以通过注册adapterType,设置GsonBuilder.registerTypeAdapter(Type, Object).来完成。
    - Gson 中默认的 Date 的表现格式和Java中是一样的如DateFormat.DEFAULT.这种格式序列化期间忽略了日期的毫秒部分。可以通过GsonBuilder.setDateFormat(int) or GsonBuilder.setDateFormat(String).来改变。
    - Gson 中默认是忽略@Expose  @ Since 的注解的,可以通过设置GsonBuilder.excludeFieldsWithoutExposeAnnotation().和GsonBuilder.setVersion(double).改变序列化和反序列化规则。
    -  默认的字段命名策略为:输出Json和Java中的对象是一样。可以通过设置 GsonBuilder.setFieldNamingPolicy(FieldNamingPolicy).来自定义这个规则。
    -  默认的情况下,Gson将擦除所有标记为瞬态或静态的字段。可以通过GsonBuilder.excludeFieldsWithModifiers(int...).来配置擦除所有类中指定的修饰符的字段。

2.2  Gson 的解析过程:

       Gson解析中序列化比较简单。这里主要说下反序列化,主要涉及到Java中的 泛型和类型的获取
       Java泛型的实现机制,使用了泛型的代码在运行期间相关的泛型参数的类型会被擦除,我们无法在运行期间获知泛型参数的具体类型(所有的泛型类型在运行时都是Object类型)。但是编译java源代码生成的Class文件中还是保存了泛型相关的信息,这些信息被保存在class字节码常量池中,使用了泛型的代码处会生成一个signature签名字段,该签名signature字段指明了这个常量池的地址,于是可从该常量池中获取到具体的类型。比如 Class类的getGenericSuperClass()方法, 对于带有泛型的class,返回一个ParameterizedType对象,对于Object、接口和原始类型返回null,对于数组class则是返回Object.class。ParameterizedType是表示带有泛型参数的类型的Java类型,JDK1.5引入了泛型之后,Java中所有的Class都实现了Type接口,ParameterizedType则是继承了Type接口,所有包含泛型的Class类都会实现这个接口。  
        对于简单的类对象,可以通过 传递一个Clazz进去,然后通过反射的方法来获得实际对象 如  (T)clazz.newInstance(),Gson中提供的方法也是如此:
public  T fromJson(String json, Class classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}
       对于复杂的集合类型中使用了泛型来限制如 List在Gson里面通过TypeToken类 更为通用的方法来有效的解决泛型的问题
public class TypeToken {
final Classsuper T> rawType;
final Type type;
final int hashCode;
@SuppressWarnings("unchecked")
protected TypeToken() {
this.type = getSuperclassTypeParameter(getClass());
this.rawType = (Classsuper T>) $Gson$Types.getRawType(type);
this.hashCode = type.hashCode();
}
注意: TypeToken类的构造方法是Protected的,使用者必须通过继承的方式,或者匿名类的方式来生成一个TypeToken对象。比如:
TypeToken> list = new TypeToken>() {};//注意:T不能使用带有通配符的参数如
Class,或
 
   
List
接下来,执行的是下面这个函数:
static Type getSuperclassTypeParameter(Class subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
由于外部不得不采用匿名类的方式来生成TypeToken,因此,需要获得的泛型类型包含在superclass中。假如你的匿名类不包含任何的泛式类型,就会抛出一个"Miss type paramter"异常。然后,执行getGenericSuperclass()操作,得到的是一个ParameterizedType 类型的Type。而这个Type包含几乎关心的所有类型元数据,包括它的拥有者,类名和泛型参数等。最后,根据静态的canonicalize 方法,规范在泛型内的参数,并且转化成Gson自己给定的数据结构,返回获取到的Type信息。具体如何获取类型的代码,可以看看Gson源码。


3   Gson 的特殊功能的使用:

参考网站: http://www.haoxiqiang.info/blog/20150608-GsonExamples.html

3.1 多个类的嵌套的例子

GSON可以很容易地序列化和反序列化静态嵌套类。
但是GSON不能自动反序列化的纯内部类,因为它们的无参构造函数需要引用的对象在反序列化时是无法使用的.

你可以通过使用静态内部类或给它提供一个定制的InstanceCreator来解决这个问题。下面是一个例子:

 
    
//NOTE: 这个class B 在默认情况下不会被`GSON`序列化
public class A {
public String a;
class B {
public String b;
publicB() {// No args constructor for B
}
}
}
}

GSON不能反序列化{"b":"abc"} ,因为class B是一个内部类,如果你这么定义B: static class B, GSON是能反序列化这段字符串的, 另外一个办法就是自定义个实例构建方法,下面是一个例子

 
    
//NOTE: 这样可行,但是不推荐
public class InstanceCreatorForB implements InstanceCreator {
privatefinal A a;
public InstanceCreatorForB(A a){
this.a= a;
}
public A.BcreateInstance(Type type){
return a.newB();
}
}

3.2 集合的例子

Gson gson =new Gson();
Collection ints = Lists.immutableList(1,2,3,4,5);
//(序列化Serialization)
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
// (反序列化Deserialization)
Type collectionType =new TypeToken>(){}.getType();
Collection ints2 = gson.fromJson(json, collectionType);//ints,ints2是一样的

集合限制

  • 能够序列化任何任意类型的集合,但是不能让它反序列化,因为使用者没有办法给指示数据类型
  • 反序列化的时候,集合一定得是一个特定泛型的集合

3.3 泛型的序列化和反序列化

当你调用toJson(obj)的时候,GSON会执行obj.getClass()来获取序列化的字段的信息,同样的,你可以在fromJson(json, MyClass.class)方法中使用典型对象.如果对象是一个非泛型对象,这样也能正常工作.但是, 如果对象是一个泛型对象,Java的类型擦除会让这个对象丢失泛型类型信息.

可以通过使用TypeToken指定正确的参数化类型的泛型类型来解决这个问题。原理就是上面2.2 Gson解析部分。


3.4 任意对象集合的序列化和反序列化

有时你会处理一些混合类型的JSON,比如

['hello',5,{name:'GREETINGS',source:'guest'}]
static class Event{
private String name;
private String source;
privateEvent(String name, String source){
this.name= name;
this.source= source;
}
@Override
public String toString(){
return String.format("(name=%s, source=%s)", name, source);
}
}

public static void main(String[] args){
Gson gson =new Gson();
Collection collection =new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS","guest"));
String json = gson.toJson(collection);
System.out.println("Using Gson.toJson() on a raw collection: "+ json);
JsonParser parser =new JsonParser();
JsonArray array = parser.parse(json).getAsJsonArray();
String message = gson.fromJson(array.get(0), String.class);
int number = gson.fromJson(array.get(1),int.class);
Event event = gson.fromJson(array.get(2), Event.class);
System.out.printf("Using Gson.fromJson() to get: %s, %d, %s", message, number, event.toString());
}

使用GSON序列化这个集合只需要调用toJson(collection),而且不用设置其他任何东西.但是你要是通过fromJson(json, Collection.class)反序列化这个集合的话是不可行的,因为GSON,没办法匹配集合类型,所以GSON需要你提供这个集合序列化的类型.有三个选则:

  • 使用GSON的解析器API(底层流解析器或DOM解析器JsonParser)来解析数据元素,然后在每一个元素上使用Gson.fromJson().这是首选的方法。
  • Collection.class注册类型适配器,让每一个元素都对应自己的对象.缺点是会搞乱GSON`中其他的集合的反序列化.
  • 通过注册一个MyCollectionMemberType使用fromJsonCollection,缺点就是只有数组是顶级元素才是可行的

3.5 从序列化和反序列化中剔除字段

GSON支持剔除顶层类,字段和字段类型,下面是一种排除字段或者类的可插拔的机制,如果这些机制没有满足你的需要.你可以自定义序列化和反序列化的解释器.

默认的情况下,如果一个对象被声明为 transient,static ,那么它就会被剔除.  如果你要包含这些字段,可以用如下代码:
 
    
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC)
.create();
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
.create();

自定义的剔除策略

如果这些默认的策略都不能满足你的需求,你还可以自定自己的策略,更多可见ExclusionStrategy
下面是一个使用@Foo的例子

 
    
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Foo {
// Field tag only annotation
}
public class SampleObjectForTest {
@Foo private final int annotatedField;
private final String stringField;
private final long longField;
private final Class clazzField;

public SampleObjectForTest() {
annotatedField = 5;
stringField = "someDefaultValue";
longField = 1234;
}
}

public class MyExclusionStrategy implements ExclusionStrategy {
private final Class typeToSkip;

private MyExclusionStrategy(Class typeToSkip) {
this.typeToSkip = typeToSkip;
}
@Override
public boolean shouldSkipClass(Class clazz) {
return (clazz == typeToSkip);
}
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(Foo.class) != null;
}
}

public static void main(String[] args) {
Gson gson = new GsonBuilder()
.setExclusionStrategies(new MyExclusionStrategy(String.class))
.serializeNulls()
.create();
SampleObjectForTest src = new SampleObjectForTest();
String json = gson.toJson(src);
System.out.println(json);
}
======== OUTPUT ========
{"longField":1234}

3.6 自定义序列化和反序列化

有时默认的内置序列化或反序列化的描述,并不是你想要的,比如做时间之类的处理的时候这种问题经常出现,Gson允许你自定义序列化工具:

  • Json Serialiers: 需要定义自定义序列化对象
  • Json Deserializers: 需要定义自定义反序列化的类型
  • Instance Creators: 无参构造方法和反序列化解析器都不是必须的
3.7  其他 

  • 紧凑漂亮的JSON输出
  • NULL对象的支持
  • 版本支持
  • 字段命名支持

你可能感兴趣的:(技术文章Android相关)