GSON-使用指南(四)

本次文章的主要内容:

  • TypeAdapter
  • JsonSerializer与JsonDeserializer
  • TypeAdapterFactory
  • @JsonAdapter注解
  • TypeAdapter与 JsonSerializer、JsonDeserializer对比
  • TypeAdapter实例

一、TypeAdapter

TypeAdapter 是Gson自2.1版本开始提供的一个抽象类,用于接管某种类型的序列化和反序列化过程,包含两个主要方法 write(JsonWriter,T) 和 read(JsonReader) 。其它的方法都是final方法并最终调用这两个抽象方法。

public abstract class TypeAdapter<T> {
    public abstract void write(JsonWriter out, T value) throws IOException;
    public abstract T read(JsonReader in) throws IOException;
}

注意:TypeAdapter 以及 JsonSerializer 和 JsonDeserializer 都需要与 GsonBuilder.registerTypeAdapter 示或GsonBuilder.registerTypeHierarchyAdapter配合使用,下面将不再重复说明。

UserTypeAdapter的定义:

public class UserTypeAdapter extends TypeAdapter<User> {//类型适配器(适配类型对象)
    @Override
    public void write(JsonWriter out, User value) throws IOException {//tojson()
        out.beginObject();
        out.name("user-name").value(value.name);//输出名称(对应属性名)-对象的值
        out.name("user-age").value(value.age);
        out.name("user-email").value(value.email);
        out.endObject();
    }
    @Override
    public User read(JsonReader in) throws IOException {//fromJson()
        User user = new User();
        in.beginObject();//开始解析对象
        while (in.hasNext()) {
            switch (in.nextName()) {//按名称解析
                case "name":
                    user.name = in.nextString()+"^_^";//nextXXX():类型转换器
                    break;
                case "age":
                    user.age = in.nextInt();
                    break;
                case "email":
                case "email_address":
                case "emailAddress":
                    user.email = in.nextString();
                    break;
            }
        }
        in.endObject();
        return user;//返回解析后的对象
    }
}

User类

public class User {
    String name;
    int age;
    String email;
    Date birth;
    String emailAddress;

    public User(String name, int age, String email, Date birth, String emailAddress) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.birth = birth;
        this.emailAddress = emailAddress;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public User(String name, int age, String email, Date birth) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.birth = birth;
    }
}

使用示例:

public static void main(String[] args) throws Exception{
    User user = new User("ktz-dg", 22);
    user.emailAddress = "123@qq.com";
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(User.class, new UserTypeAdapter())//个性化定制-为User注册TypeAdapter实现
            .create();
    System.out.println(gson.toJson(user));//{"user-name":"ktz-dg","user-age":22}
    String jsonStr="{\"name\":\"loser\",\"age\":22}";

    user=gson.fromJson(jsonStr,User.class);
    System.out.println(user);//cc.xiaoshanbdacsi.User@649d209a
    System.out.println(user.name+","+user.age);//loser^_^,22
}

 当我们为User.class 注册了 TypeAdapter之后,只要是操作User.class 那些之前介绍的@SerializedName 、FieldNamingStrategy、Since、Until、Expos通通都黯然失色,失去了效果,现在只会调用实现的UserTypeAdapter.write(JsonWriter, User) 方法,想怎么写就怎么写。

再说一个场景,Gson有一定的容错机制,比如将字符串 “24” 会转成int 的24,但如果有些情况下给你返了个空字符串怎么办?虽然这是服务器端的问题,但这里只是做一个示范。

int型会出错是吧,根据我们上面介绍的,注册一个TypeAdapter 把序列化和反序列化的过程接管不就行了?

public static void main(String[] args) throws Exception{
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Integer.class, new TypeAdapter<Integer>() {//个性化定制-注册类型适配器(Integer类型)
                @Override
                public void write(JsonWriter out, Integer value) throws IOException {//toJson()-生成
                    out.value(String.valueOf(value));
                }
                @Override
                public Integer read(JsonReader in) throws IOException {//fromJson()-解析
                    try {
                        return Integer.parseInt(in.nextString());
                    } catch (NumberFormatException e) {
                        return -1;
                    }
                }
            })
            .create();
    System.out.println(gson.toJson(100)); // "100"
    System.out.println(gson.fromJson("\"\"",Integer.class)); // -1
}

注:测试空串的时候一定是”\”\””而不是””,””代表的是没有json串,”\”\””才代表json里的””。

你说这一接管就要管两样好麻烦啊,我明明只想管序列化(或反列化)这一个过程,另一个过程我并不关心,难道没有其它更简单的方法么? 当然有!就是接下来要介绍的 JsonSerializer与JsonDeserializer。

二、JsonSerializer与JsonDeserializer

JsonSerializer 和JsonDeserializer 不用像TypeAdapter一样,必须要实现序列化和反序列化的过程,你可以根据需要选择,比如只接管序列化的过程就用 JsonSerializer ,只接管反序列化的过程就用 JsonDeserializer 。

上面的需求可以使用如下代码:

public static void main(String[] args) throws Exception{
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Integer.class, new JsonDeserializer<Integer>() {//反序列化定制-针对Integer部分的处理
                @Override
                public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {//fromJson()
                    try {
                        return json.getAsInt();
                    } catch (NumberFormatException e) {
                        return -1;
                    }
                }
            })
            .create();
    System.out.println(gson.toJson(100)); //序列化:100
    System.out.println(gson.fromJson("\"\"", Integer.class)); //反序列化:-1

    User user = gson.fromJson("{\"name\":\"saojun\",\"age\":21}", User.class);
    System.out.println(user.name+", "+user.age); //反序列化: saojun, 21
}

下面是所有数字都转成序列化为字符串的例子:

public static void main(String[] args) throws Exception{
    //JSON序列化器
    JsonSerializer<Number> numberJsonSerializer = new JsonSerializer<Number>() {//序列化定制-针对Number部分的处理
        @Override
        public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {//toJson()
            return new JsonPrimitive(String.valueOf(src));
        }
    };
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(Integer.class, numberJsonSerializer)
            .registerTypeAdapter(Long.class, numberJsonSerializer)
            .registerTypeAdapter(Float.class, numberJsonSerializer)
            .registerTypeAdapter(Double.class, numberJsonSerializer)
            .create();
    System.out.println(gson.toJson(100.0f));//"100.0"
}

注:registerTypeAdapter必须使用包装类型,所以int.class,long.class,float.class和double.class是行不通的。同时不能使用父类来替上面的子类型,这也是为什么要分别注册而不直接使用Number.class的原因。

上面特别说明了registerTypeAdapter不行,那就是有其它方法可行咯?当然!换成registerTypeHierarchyAdapter就可以使用Number.class而不用一个一个单独注册了。

registerTypeAdapter与registerTypeHierarchyAdapter的区别:

注意:如果一个被序列化的对象本身就带有泛型,且注册了相应的TypeAdapter,那么必须调用Gson.toJson(Object,Type),明确告诉Gson对象的类型。另外,可以简单理解beginArray()为[,beginArray()为]。

public static void main(String[] args) throws Exception{
    Type type = new TypeToken<List<User>>() {}.getType();//json针对于泛型的解析助手,当然包括基本的解析功能
    TypeAdapter typeAdapter = new TypeAdapter<List<User>>() {
        @Override
        public void write(JsonWriter out, List<User> value) throws IOException {//toJson()-反序列化
            out.beginArray();//[
            for (User item:value){
                out.beginObject();//{
                out.name("name").value(item.name);//输出名称(对应属性名)-对象的值
                out.name("age").value(item.age);
                out.endObject();//}
            }
            out.endArray();//]
        }
        @Override
        public List<User> read(JsonReader in) throws IOException {
            return null;
        }
    };
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(type, typeAdapter)
            .create();
    List<User> list = new ArrayList<>();
    list.add(new User("x1",21));
    list.add(new User("x2",22));
    //注意,多了个type参数
    String result = gson.toJson(list, type);
    System.out.println(result);//[{"name":"x1","age":21},{"name":"x2","age":22}]
}

三、TypeAdapterFactory

TypeAdapterFactory,见名知意,用于创建TypeAdapter的工厂类,通过对比Type类型,确定有没有对应的TypeAdapter,没有就返回null,与GsonBuilder.registerTypeAdapterFactory配合使用。

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(new TypeAdapterFactory() {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            return null;
        }
    })
    .create();

四、@JsonAdapter注解

JsonAdapter相较之前介绍的FieldNamingStrategy以及SerializedName 、Since、Until、Expos这几个注解都是比较特殊的,其它的几个都是用在POJO的字段上,而这一个是用在POJO类上的,接收一个参数,且必须是TypeAdpater,JsonSerializer或JsonDeserializer这三个中的一个。

上面说JsonSerializer和JsonDeserializer都要配合GsonBuilder.registerTypeAdapter使用,但每次使用都要注册也太麻烦了,JsonAdapter就是为了解决这个痛点的。

使用方法(以User为例):

//@JsonAdapter(UserTypeAdapter.class) //加在类上,优先级高于@SerializedName注解
public class User2 {
    public String name;
    public int age;
    @SerializedName(value = "emailAddress") //映射后输出的"key"值,注释掉@JsonAdapter才生效
    public String email;
    public User2() {
    }
    public User2(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public User2(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

自定义类型适配器

public class UserTypeAdapter extends TypeAdapter<User2> {//类型适配器(适配类型对象)
    @Override
    public void write(JsonWriter out, User2 value) throws IOException {//tojson()
        out.beginObject();
        out.name("User2-name").value(value.name);//输出名称(对应属性名)-对象的值
        out.name("User2-age").value(value.age);
        out.name("User2-email").value(value.email);
        out.endObject();
    }
    @Override
    public User2 read(JsonReader in) throws IOException {//fromJson()
        User2 User2 = new User2();
        in.beginObject();//开始解析对象
        while (in.hasNext()) {
            switch (in.nextName()) {//光标位在第一个属性名之前
                case "name":
                    User2.name = in.nextString()+"^_^";//nextXXX():类型转换器
                    break;
                case "age":
                    User2.age = in.nextInt();
                    break;
                case "email":
                case "email_address":
                case "emailAddress":
                    User2.email = in.nextString();
                    break;
            }
        }
        in.endObject();
        return User2;//返回解析后的对象
    }
}

使用时不用再使用 GsonBuilder去注册UserTypeAdapter了。

注: @JsonAdapter 仅支持 TypeAdapter或TypeAdapterFactory( 2.7开始已经支持 JsonSerializer/JsonDeserializer)

public static void main(String[] args) throws Exception{
    Gson gson = new Gson();
    User2 user = new User2("ktz-dg", 22, "123@qq.com");
    System.out.println(gson.toJson(user));
    //{"User2-name":"ktz-dg","User2-age":22,"User2-email":"123@qq.com"}
    //为区别结果,特意把email字段与@SerializedName注解中设置的不一样
}

注意:JsonAdapter的优先级比GsonBuilder.registerTypeAdapter的优先级更高。

五、TypeAdapter与 JsonSerializer、JsonDeserializer对比

 

六、TypeAdapter实例

注:这里的TypeAdapter泛指TypeAdapter、JsonSerializer和JsonDeserializer。

这里的TypeAdapter 上面讲了一个自动将字符串形式的数值转换成int型时可能出现空字符串的问题,下面介绍一个其它的需求:

服务器返回的数据中data字段类型不固定,比如请求成功,data是一个List,不成功的时候是String类型,这样前端在使用泛型解析的时候,怎么去处理呢?

其实这个问题的原因主要由服务器端造成的,接口设计时没有保证数据的一致性,正确的数据返回姿势:同一个接口任何情况下不得改变返回类型,要么就不要返回,要么就返回空值,如null、[],{}。

但这里还是给出解决方案:

方案一:

Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {
    @Override
    public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json.isJsonArray()){
            //这里要自己负责解析了
            Gson newGson = new Gson();
            return newGson.fromJson(json,typeOfT);
        }else {
            //和接口类型不符,返回空List
            return Collections.EMPTY_LIST;
        }
    }
}).create();

方案二:

public static void main(String[] args) throws Exception{
    Gson gson = new GsonBuilder()
            .registerTypeHierarchyAdapter(List.class, new JsonDeserializer<List<?>>() {
        @Override
        public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            if (json.isJsonArray()) {
                JsonArray array = json.getAsJsonArray();
                Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];//获取<>中多个参数的实际类型数组的第一个参数的类型,比如<String,Object>就是String的类型
                List list = new ArrayList<>();
                for (int i = 0; i < array.size(); i++) {
                    JsonElement element = array.get(i);//获得第N个JSON对象
                    Object item = context.deserialize(element, itemType);//对第N个JSON对象反序列化
                    list.add(item);//反序列化后的java对象添入集合中
                }
                return list;
            } else {
                //和接口类型不符,返回空List
                return Collections.EMPTY_LIST;
            }
        }
    }).create();
}

关于getActualTypeArguments()

  • 这个方法的作用就是获取泛型<>中所有实际类型参数的Type数组,比如<String,Number>获取到的数组中第一个元素就是String的类型,第二个元素就是Number的类型。
  • 代码是最好的说明:
public class TestgetGenericType {
    Map<String, Number> collection;
    public static void main(String[] args)throws Exception {
        Class<?> clazz = TestgetGenericType.class; //取得 Class
        Field field = clazz.getDeclaredField("collection"); //取得类中的字段
        Type type = field.getGenericType(); //取得泛型<>中的类型
        System.out.println("【泛型<>的Type】"+type+"\n");
        ParameterizedType ptype = (ParameterizedType) type; //强制转换成参数化类型
        System.out.println(ptype.getActualTypeArguments()[0]); //取出第一个参数的实际类型
        System.out.println(ptype.getActualTypeArguments()[1]); //取出第二个参数的实际类型
        /**
         * 【泛型<>的Type】java.util.Map<java.lang.String, java.lang.Number>
         * class java.lang.String
         * class java.lang.Number
         */
    }
}

要注意的点:

  • 必须使用registerTypeHierarchyAdapter方法,不然对List的子类无效,但如果POJO中都是使用List,那么可以使用registerTypeAdapter。
  • 对于是数组的情况,需要创建一个新的Gson,不可以直接使用context,不然gson又会调我们自定义的JsonDeserializer造成递归调用,方案二没有重新创建Gson,那么就需要提取出List<?>中?的类型,然后分别反序列化。适合手动注册了TypeAdaper的情况:registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter)。
  • 从效率上推荐方案二,免去重新实例化Gson和注册其它TypeAdapter的过程。