Gson的那些常见使用方法你都知道吗

1.什么是Gson

Gsongoogle提供的一种基于Java的工具库,可以把Java对象转化为Json字符串,也可以把Json格式的字符串转化为Java对象。而Json常用于网络数据结构的传输,H5的数据操作也经常会用到json的数据结构

2.转换的基本用法

说起转换,我们经常用到的两个方法是toJsonfromJson,一个是把对象转成json字符串,一个是把json字符串转成对象。
然而对于Gson来说,能转换的并不是严格要求是完全正确的,或者说是格式良好的Json。对于基本数据类型Gson也是可以识别的,这就增加了很多灵活性。
比如我们写几个数据格式进行测试

public static void main(String[] args) {
        A a = new A("AName", 18);
        A a1 = new A("AName 2 ", 19);
        List<A> list = new ArrayList<>();
        list.add(a);
        list.add(a1);

        HashMap<String, Integer> map1 = new HashMap<>();
        map1.put("key1", 1);
        map1.put("key2", 2);

        HashMap<String, A> map2 = new HashMap<>();
        map2.put("key3", a);
        map2.put("key4", a1);

        Gson gson = new Gson();

        String v1 = gson.toJson(1);
        String v2 = gson.toJson(1, int.class);
        String v3 = gson.toJson("1", String.class);
        String v4 = gson.toJson(new int[]{1, 2, 3});

        String v5 = gson.toJson(a);
        String v6 = gson.toJson(list);
        String v7 = gson.toJson(map1);
        String v8 = gson.toJson(map2);

        int v11 = gson.fromJson("1", int.class);
        String v12 = gson.fromJson("1", String.class);
        int[] v13 = gson.fromJson("[1,2,3]",int[].class);

        A v14 = gson.fromJson(v5, A.class);
        List<A> v15 = gson.fromJson(v6, List.class);
        HashMap<String,Integer> v16 = gson.fromJson(v7,HashMap.class);
        HashMap<String,A> v17 = gson.fromJson(v8,HashMap.class);

        printResult("v1", v1);
        printResult("v2", v2);
        printResult("v3", v3);
        printResult("v4", v4);
        System.out.println("=================================================");
        printResult("v5", v5);
        printResult("v6", v6);
        printResult("v7", v7);
        printResult("v8", v8);
        System.out.println("=================================================");
        printResult("v11", v11);
        printResult("v12", v12);
        printResult("v13", v13);
        System.out.println("v13 detail : " + Arrays.toString(v13));
        System.out.println("=================================================");
        printResult("v14", v14);
        printResult("v15", v15);
        printResult("v16", v16);
        printResult("v17", v17);
    }
    private static void printResult(String tag, Object value) {
        String result = tag + " : " + value.getClass().getTypeName() + " ->> " + value;
        System.out.println(result);
    }
}
class A {
    String name;
    int age;
    public A(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

我这里定义了几个基本数据类型格式,同时定义了常规的对象类以及List,甚至Map的格式进行存储并转换,输出结果是

v1 : java.lang.String ->> 1
v2 : java.lang.String ->> 1
v3 : java.lang.String ->> "1"
v4 : java.lang.String ->> [1,2,3]
=================================================
v5 : java.lang.String ->> {"name":"AName","age":18}
v6 : java.lang.String ->> [{"name":"AName","age":18},{"name":"AName 2 ","age":19}]
v7 : java.lang.String ->> {"key1":1,"key2":2}
v8 : java.lang.String ->> {"key3":{"name":"AName","age":18},"key4":{"name":"AName 2 ","age":19}}
=================================================
v11 : java.lang.Integer ->> 1
v12 : java.lang.String ->> 1
v13 : int[] ->> [I@108c4c35
v13 detail : [1, 2, 3]
=================================================
v14 : com.example.test.A ->> A{name='AName', age=18}
v15 : java.util.ArrayList ->> [{name=AName, age=18.0}, {name=AName 2 , age=19.0}]
v16 : java.util.HashMap ->> {key1=1.0, key2=2.0}
v17 : java.util.HashMap ->> {key3={name=AName, age=18.0}, key4={name=AName 2 , age=19.0}}

可以看出,对于基本数据类型格式,以及对象,集合List,甚至MapGson都能正常的识别,并正确转换成所声明匹配的格式。当然,所匹配的类格式必须正确,否则也是会抛出异常的。

3.Gson那些常用的注解

1.SerializedName

当我们定一个可以被Gson解析的类,或者用GsonFormat工具去自动生成可以被匹配的类时,我们会发现,所有属性名称都必须严格按照返回的Json格式去定义,因为Gson会通过反射去获取所有成员属性的名称,按照这个名去定义属性名,也就是说一个字母之差,Gson都会认为这个属性为空,会导致解析失败或者直接给这个属性赋空值。而正因为这个特性,我们所定义的JSON类在使用的时候完全不能被混淆。

SerializedName是一个属性的标记注解,可以把定义类的成员变量属性名和返回的Json中的格式名称隔离开。
SerializedName中定义返回Json格式中所对应的的Key值,那么Gson在解析的时候就会以SerializedName中定义的格式为准,而不会去反射去获取并处理成员变量的属性名称。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {
  String value();
  String[] alternate() default {};
}

这个注解里有两个具体的可设置的参数
value 这个是默认值,就是Json会匹配这个值,并把这个值当做Key去解析数据
alternate 这个是数据的拓展,而且可以传多个值。当valuealternate都有值时,如果只有一个匹配到,那么就会按照匹配到的这个去解析,如果有多个值,那么会以Json格式的最后面的Key为准。

 public static void main(String[] args) {
        Gson gson = new Gson();
        TestA testA = new TestA("Name-A", 18);
        String json = gson.toJson(testA);
        System.out.println("json: " + json);
        TestA result = gson.fromJson(json, TestA.class);
        System.out.println("result : " + result);

        String testJson1 = "{\"name_1\":\"Name-A-1\",\"age\":18}";
        String testJson2 = "{\"name_2\":\"Name-A-2\",\"age\":19}";
        String testJson3 = "{\"name_3\":\"Name-A-3\",\"age\":20}";

        TestA restA1 = gson.fromJson(testJson1, TestA.class);
        System.out.println("result-1 : " + restA1);

        TestA restA2 = gson.fromJson(testJson2, TestA.class);
        System.out.println("result-2 : " + restA2);

        TestA restA3 = gson.fromJson(testJson3, TestA.class);
        System.out.println("result-3 : " + restA3);
    }
    
class TestA {
    @SerializedName(value = "name", alternate = {"name_1", "name_2"})
    String a;
    @SerializedName("age")
    int b;
    public TestA(String a, int b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public String toString() {
        return "TestA{" +
                "a='" + a + '\'' +
                ", b=" + b +
                '}';
    }
}

我们的属性名称定义为了a和b,而替代的是在SerializedName中定义了相关的key。输出结果是

json: {"name":"Name-A","age":18}
result : TestA{a='Name-A', b=18}
result-1 : TestA{a='Name-A-1', b=18}
result-2 : TestA{a='Name-A-2', b=19}
result-3 : TestA{a='null', b=20}

可以看出,对象转成Json的时候,会按照定义的SerializedNamevalue中的去生成。解析的时候,只要定义的valuealternate中的有一个匹配,那么就会把这个当做Key去生成对象。

alternate的另一种场景是多个Json的Key的匹配。可以参考这么一种场景,比如原来Json中有一个Key的值是 name,后来发现这个名字不太好识别,想要把这个Key值修改为name1,然后在新版本中加了这个字段,为了兼容老版本,原来的字段也会保留,那么就可能同时出现namename1两个字段,这个时候又会是怎么样的呢。比如上面的例子中,我这里新建一个字符串的解析

public static void main(String args[]){
	String testJson0 = "{\"name\":\"Name-A\",\"name_1\":\"Name-A-1\",\"age\":18}";
    TestA restA0 = gson.fromJson(testJson0,TestA.class);
    System.out.println("result-0 : "+restA0);
}

这时候的输出结果是

result-0 : TestA{a='Name-A-1', b=18}

到这里你可能会以为alternate的优先级会比value要高,会优先匹配alternate,但如果我把上面的字符串内容换个位置

public static void main(String args[]){
	String testJson0 = "{\"name_1\":\"Name-A-1\",\"name\":\"Name-A\",\"age\":18}";
    TestA restA0 = gson.fromJson(testJson0,TestA.class);
    System.out.println("result-0 : "+restA0);
}

输出结果是

result-0 : TestA{a='Name-A', b=18}

可以看出会以最后解析到的字符串赋值为准,可以类比成一个Map,后面的值会覆盖掉前面的,所以服务器在设计的时候也得有一定顺序规范才可以

2.Expose

Expose定义了Gson的解析和生成过程中的序列化操作,和transient有点类似

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {
  public boolean serialize() default true;
  public boolean deserialize() default true;
}

serialize 定义对象类转String时候可以序列化,默认是true
deserialize 定义String转对象操作可以反序列化,默认是true
或者说就是定义哪些字段可以被解析,可以被解析的会按照上面的规范来操作,不可以被解析的,在操作过程中会丢弃或者赋空值

public static void main(String[] args) {
        Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
        TestA testA = new TestA("Name-A", 18);
        String json = gson.toJson(testA);
        System.out.println("json: " + json);
        
 	    String json1= "{\"name\":\"Name-A\",\"age\":18}";
        TestA result = gson.fromJson(json1, TestA.class);
        System.out.println("result : " + result);
 }
        
class TestA {
    @Expose()
    @SerializedName(value = "name")
    String a;

    @Expose(serialize = true, deserialize = false)
    @SerializedName("age")
    int b;

    public TestA(String a, int b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public String toString() {
        return "TestA{" +
                "a='" + a + '\'' +
                ", b=" + b +
                '}';
    }
}

还是上面的例子,这里注意,Gson的创建方式变成了GsonBuilder,同时需要设置excludeFieldsWithoutExposeAnnotation这个方法。
然后我定义name是可以双向转换的,定义age是可以序列化但不可以反序列化,输出结果是

json: {"name":"Name-A","age":18}
result : TestA{a='Name-A', b=0}

可以看出序列化正常,而反序列化则不会处理,这里是返回一个默认值
然后把age的Expose属性对调,再输出一次

json: {"name":"Name-A"}
result : TestA{a='Name-A', b=18}

可以看出,序列化阶段,标记serialize = false的直接会被丢弃,不写入json串中,而反序列化则是正常的

3.Since

Since 是用于版本控制的,用于定义从哪个版本开始支持序列化和反序列化,比这个版本低的则不会处理。
需要通过下面的方法设置版本号setVersion

Gson gson = new GsonBuilder().setVersion(1).create();

....
class Test{
	@Since(2)
	String name;
	.....
}

这个很简单,同样是给属性标记,可以理解成当当前的Gson设置的版本大于等于标记的版本时,才会去处理序列化和反序列化,注意这里是一对处理,相当于Expose的两个属性都为true或flase

这个例子里Gson设置的版本为1,而since的版本是2,那么这个name不论序列化和反序列化都不会参与。把Gson设置的版本修改成2就可以支持了

4.Until

Until 这个和Since类似,功能也一样,只是Since是控制下界,而Until是控制上界的,也就是最多支持到多少版本。

4.GsonBuilder的那些配置

serializeNulls 设置允许null类型的值进行序列化。
默认情况下,当我们需要序列化的对象的某个属性为空时,序列化转成的json字符串会剔除掉这个属性而不写入Json中,设置这个属性,那么即使属性为null,也会写入,即会生成一个"key":null的属性键值对

setPrettyPrinting 设置序列化的字符串输出格式。
这个方法设置后,序列化后的字符串会对每个属性键值对以及开闭的括号符号后进行换行,转成成一个可读性很强的字符串。实际就转换成我们平常在线用网页格式化看JSON的那种结构,各个属性都有明确的换行。

setVersion 设置当前的版本号。

disableHtmlEscaping 设置特殊字符不会转义。
这个主要用于Html,一些特殊字符比如<>等在序列化时候会被转义而丢失,设置这个属性则会保留这些字符不被转义。

disableInnerClassSerialization 设置内部类不能被序列化。可以类比成内部类的属性带有transient不可序列化的标记。

setDateFormat 设置日期格式化的格式。如果对象类中有Date属性会用到。

setLongSerializationPolicy 设置Long类型的序列化策略。
这个目前只有两种,DEFAULTSTRING。保持不变,或者序列化时候把这个属性在JSON中转成String,就是"Key":1转成"Key":"1"

setFieldNamingPolicy 设置成员属性的命名策略。
这个一般如果我们定义的属性是以驼峰命名方式命名的话会生效,比如我们定义了一个驼峰属性名 aBcD,那么会有以下几种情况:

FieldNamingPolicy状态效果
IDENTITY保持不变aBc
UPPER_CAMEL_CASE首字母大写ABcD
UPPER_CAMEL_CASE_WITH_SPACES首字母大写,驼峰处加空格A Bc D
LOWER_CASE_WITH_UNDERSCORES转小写,驼峰处加下划线a_bc_d
LOWER_CASE_WITH_DASHES转小写,驼峰处加中线a-bc-d
LOWER_CASE_WITH_DOTS转小写,驼峰处加点a.bc.d

setFieldNamingStrategy 设置成名变量名称的定义策略,需要重写一个translateName,在这里可以统一定义属性名称的命名规则,是setFieldNamingPolicy的自定制的版本。

generateNonExecutableJson 生成一个非可执行的Json。起始就是生成)]}'这样的4个字符,目前用处未知。

setLenient 设置忽略Json格式上的错误。也就是说解析并非需要是完全匹配的格式,一般我们的json如果有格式上的错误会抛出异常,加这个属性后就会忽略掉这些。但是不建议使用。

enableComplexMapKeySerialization 允许复杂的Map的Key的序列化。就是说,Map的Key如果是对象,并且这个对象定义了可序列化,那么序列化这个Map的时候,就会以这个对象序列化的Json字符串作为这个Map的Key,实际用处感觉不大。如果不设置,那么就会以对象的toString的方法的字符串值作为Map的Key。

excludeFieldsWithModifiers 设置哪些Modifier声明的属性不能被序列化。比如传Modifier.PRIVATE表示私有的private的属性不能被序列化。这里可以传多个值进去。

excludeFieldsWithoutExposeAnnotation 设置未标记Expose注解的属性不能被序列化,这上面有提到。

setExclusionStrategies 设置不可序列化的策略,可以传入多个。ExclusionStrategy这个有两个可重写的方法shouldSkipFieldshouldSkipClass,分别表示要剔除哪些属性和类的序列化。默认返回是false,也就是默认是可以被序列化的。这里的序列化包括序列化和反序列化。

addSerializationExclusionStrategy 设置不可序列化的策略,和setExclusionStrategies用法一致,只是这个是单向的序列化的排除策略。

addDeserializationExclusionStrategy 设置不可反序列化的策略,用法同上。

5.TypeToken

我们在定义类的时候,当某些类的结构满足同一个模板,那么久会使用到泛型去统一声明。而因为泛型擦除的存在,泛型在虚拟机加载时会被擦除为Object类型,这样我么你在反序列化时候就会出现问题。

class ResponseWrapper<T> {
    int code;
    T data;
    //省略了  get/set/toString方法
}

class Person {
    String name;
    int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //省略了  get/set/toString方法
}

public static void main(String args[]){
	String json  ="{\"code\":1,\"data\":{\"name\":\"Lucky\",\"age\":18}}";
	ResponseWrapper<Person> response = new Gson().fromJson(json, ResponseWrapper.class);
    System.out.println("result ->>> " + response);
    Object object = response.getData();
    System.out.println("match ->>> "+(object instanceof Person));
}

这个是我们常用的数据的返回通用模板,而fromJson方法中是不可以传递泛型的,所以对于泛型,这里会当做Object类型去解析,这里的输出结果是

result ->>> ResponseWrapper{code=1, data={name=Lucky, age=18.0}}
match ->>> false

可以看出,Person类并没有被正确解析,返回的格式并不是我们所想要的。

TypeToken则是用来处理这种泛型问题的,先看下TypeToken的用法

public static void main(String args[]){
	  String json  ="{\"code\":1,\"data\":{\"name\":\"Lucky\",\"age\":18}}";
 	  Type type = new TypeToken<ResponseWrapper<Person>>(){} .getType();
      ResponseWrapper<Person> response = new Gson().fromJson(json,type);
      System.out.println("result ->>> " + response);
      Object object = response.getData();
      System.out.println("match ->>> "+(object instanceof Person));
    }
}

可以看出,和上面的代码只有一个Type的区别,而输出结果是

result ->>> ResponseWrapper{code=1, data=Person{name='Lucky', age=18}}
match ->>> true

这里可以看出Person泛型也被正确的解析出来了,那么究竟是什么原理呢,可以看下TypeToken的具体实现

public class TypeToken<T> {
  final Type type;
  @SuppressWarnings("unchecked")
  protected TypeToken() {
    this.type = getSuperclassTypeParameter(getClass());
	......
  }
  public final Type getType() {
    return type;
  }
  
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]);
  }
	......
}

可以看出这里是是获取到了定义的泛型参数,然后把具体的泛型参数类型传递给Gson进行解析,那么我们就可以模仿这个去写一个简单的自己的TypeToken。

public class MyTypeToken<T> {
    private final Type type;
    protected MyTypeToken() {
        Type genericType = getClass().getGenericSuperclass();
        System.out.println("genericType ->>> "+genericType);
        ParameterizedType parameterizedType = (ParameterizedType) genericType;
        this.type = parameterizedType.getActualTypeArguments()[0];
        System.out.println("resultType ->>> "+type);
    }
    public Type getType() {
        return type;
    }
}

代码很简单,这里getGenericSuperclass是获取当前类的直接父类的包含泛型参数的Type类型,然后强转成ParameterizedType,这个就是参数化类型的实际类型表示。而一个类的泛型可能包含多个,这里获取到了泛型中的第一个,然后返回。
最后把上面的Gson的替换成我们的

  Type mType = new MyTypeToken<ResponseWrapper<Person>>(){}.getType();

输出结果是

genericType ->>> com.example.test.ann.MyTypeToken<com.example.test.ResponseWrapper<com.example.test.Person>>
resultType ->>> com.example.test.ResponseWrapper<com.example.test.Person>
result ->>> ResponseWrapper{code=1, data=Person{name='Lucky', age=18}}
match ->>> true

可以看出getGenericSuperclass返回的是包含MyTypeToken这个类在内的所对应直接泛型信息,而getActualTypeArguments获取的是实际的每个具体的泛型信息。
同时可以看出,我这里定义的方法为protected,那么就意味着,其他地方创建的时候必须以匿名内部类的方式创建,也就是后面会添加{}这个表示。因为如果是对象的话,那么里面的泛型会被擦除,我们获取的时候就是Object了,无法进行类型的转换。

6.TypeAdapter

TypeAdapter提供了我们自定制序列化和反序列化的操作,需要进行注册,注册完成后,原解析的toJsonfromJson都会从这个adapter去操作然后取出详细的结果,因为操作过程很繁琐,平常我们基本很少会用到,下面举一个简单的操作。

class Person {
    private String name;
    private int age;
    private List<Work> works;
   	//省略部分 set/get/toString/构造方法
}

class Work {
    private int id;
    private String workName;
	//省略部分 set/get/toString/构造方法
}

这是声明的操作的类,我这里定义了两个基本数据类型,和一个关联的外部类

class PersonTypeAdatper extends TypeAdapter<Person> {
    @Override
    public void write(JsonWriter out, Person person) throws IOException {
        out.beginObject()
                .name("_name").value(person.getName())
                .name("_age").value(person.getAge())
                .name("_works");
        //写入array集合
        List<Work> works = person.getWorks();
        out.beginArray();
        for (Work work : works) {
            out.beginObject().name("_id").value(work.getId())
                    .name("_workName").value(work.getWorkName()).endObject();
        }
        out.endArray();
        out.endObject();
        out.flush();
        out.close();
    }

    @Override
    public Person read(JsonReader in) throws IOException {
        Person person = new Person();
        List<Work> works = new ArrayList<>();
        in.beginObject();
        while (in.hasNext()) {
            JsonToken jsonToken = in.peek();
            System.out.println("jsonToken ->>> " + jsonToken);
            switch (jsonToken) {
                case NAME:
                    readNextPersonName(in, person);
                    break;
                case BEGIN_ARRAY:
                    in.beginArray();
                    JsonToken objToken = null;
                    do {
                        in.beginObject();
                        Work work = new Work();
                        for (; ; ) {
                            readNextWorkName(in, work);
                            JsonToken workToken = in.peek();
                            if (workToken == JsonToken.END_OBJECT) break;
                        }
                        works.add(work);
                        in.endObject();
                        objToken = in.peek();
                    } while (objToken != JsonToken.END_ARRAY);

                    in.endArray();
                    break;
            }
        }
        in.endObject();
        person.setWorks(works);
        return person;
    }
    private void readNextPersonName(JsonReader reader, Person person) throws IOException {
        String name = reader.nextName();
        switch (name) {
            case "_name":
                person.setName(reader.nextString());
                break;
            case "_age":
                person.setAge(reader.nextInt());
                break;
            case "_works":
                break;
        }
    }
    private void readNextWorkName(JsonReader reader, Work work) throws IOException {
        String name = reader.nextName();
        switch (name) {
            case "_id":
                work.setId(reader.nextInt());
                break;
            case "_workName":
                work.setWorkName(reader.nextString());
                break;
        }
    }
}

继承TypeAdapter需要重写两个序列化的方法,read表示反序列化,write表示序列化。
read需要我们自己构建一个实体类,然后设置详细的参数属性最后返回,而write则需要我们根据提供的实体类去组装Json数据结构。

可以通过peek方法获取下一条对应的标记,这个不会消耗这个偏移量,标记基本上来说是类型枚举,比如是否是JsonObject或JsonArray的开始或结束标签,是否是Json中对应的Key等等。

可以看出,反序列化的操作会很繁琐,对于结构复杂的json那就得写到天荒地老了。。。

   Person person = new Person("Lucky", 18);
        List<Work> works = new ArrayList<>();
        works.add(new Work(1, "洗衣服"));
        works.add(new Work(2, "扫地"));
        person.setWorks(works);

        Gson gson = new GsonBuilder().registerTypeAdapter(Person.class, new PersonTypeAdatper()).create();
        String json = gson.toJson(person);
        System.out.println("json ->>> " + json);
        Person resPerson = gson.fromJson(json, Person.class);
        System.out.println("resPerson ->>> " + resPerson);

这个是调用的方法,需要调用registerTypeAdapter方法去配置信息。输出结果是:

json ->>> {"_name":"Lucky","_age":18,"_works":[{"_id":1,"_workName":"洗衣服"},{"_id":2,"_workName":"扫地"}]}
jsonToken ->>> NAME
jsonToken ->>> NAME
jsonToken ->>> NAME
jsonToken ->>> BEGIN_ARRAY
resPerson ->>> Person{name='Lucky', age=18, works=[Work{id=1, workName='洗衣服'}, Work{id=2, workName='扫地'}]}

可以看出和我们预想的结果是一致的,我这里打印了操作的标签,其中NAME就是Json中的Key,BEGIN_AARRAY就是JsonArray开始的标记。

7.@JsonAdapter

对于6中的注册过程我们可以省去,用@JsonAdapter去标记所要自定义序列化操作的类即可实现同样的效果

@JsonAdapter(value = PersonTypeAdatper.class)
class Person {
	...
}

比如上面的例子就可以这么表示,然后registerTypeAdapter方法就可以不去调用了

8.JsonDeserializer,JsonSerializer

这个和TypeAdapter类似,也是自定义去处理序列化和反序列化的操作过程的,不同的是这个可以用我们熟悉的JSONOBJECT的模式去处理。
Gson提供了JsonObjectJsonArray去处理自定义的序列化操作,用法和原生的差不多。
操作也很简单,这里就举一个例子,不再详细描述。

  public static void main(String[] args) {
        Person person = new Person("Lucky", 18);
        List<Work> works = new ArrayList<>();
        works.add(new Work(1, "洗衣服"));
        works.add(new Work(2, "扫地"));
        person.setWorks(works);

        Gson gson = new GsonBuilder()
        		.registerTypeAdapter(Person.class, serializer)
                .registerTypeAdapter(Person.class, deserializer).create();
        String json = gson.toJson(person);
        System.out.println("json ->>> " + json);
        Person resPerson = gson.fromJson(json, Person.class);
        System.out.println("resPerson ->>> " + resPerson);
    }

    static JsonDeserializer deserializer = new JsonDeserializer<Person>() {
        @Override
        public Person deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                JsonObject jsonObject = json.getAsJsonObject();
                Person person = new Person();
                person.setName(jsonObject.get("__name").getAsString());
                person.setAge(jsonObject.get("__age").getAsInt());
                JsonArray array = jsonObject.get("__works").getAsJsonArray();
                List<Work> works = new ArrayList<>();
                for (int i = 0; i < array.size(); i++) {
                    JsonObject item = array.get(i).getAsJsonObject();
                    Work work = new Work();
                    work.setId(item.get("__id").getAsInt());
                    work.setWorkName(item.get("__workName").getAsString());
                    works.add(work);
                }
                person.setWorks(works);
                return person;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    };

    static JsonSerializer serializer = new JsonSerializer<Person>() {
        @Override
        public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) {
            try {
                JsonObject jsonObject = new JsonObject();
                jsonObject.addProperty("__name", src.getName());
                jsonObject.addProperty("__age", src.getAge());
                JsonArray array = new JsonArray();
                for (Work work : src.getWorks()) {
                    JsonObject item = new JsonObject();
                    item.addProperty("__id", work.getId());
                    item.addProperty("__workName", work.getWorkName());
                    array.add(item);
                }

                jsonObject.add("__works", array);
                return jsonObject;
            } catch (Exception e) {
                e.printStackTrace();
            }

            return null;
        }
    };

输出结果是

json ->>> {"__name":"Lucky","__age":18,"__works":[{"__id":1,"__workName":"洗衣服"},{"__id":2,"__workName":"扫地"}]}
resPerson ->>> Person{name='Lucky', age=18, works=[Work{id=1, workName='洗衣服'}, Work{id=2, workName='扫地'}]}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值