Life is short , you need gson

简介: 人生苦短,我用GSON

一、JSON简介


JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。 JSON 键值对是用来保存JS对象的一种方式,和JS对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值,如下例子所示:

{"firstName": "John"}


二、谷歌Gson


解析和生成json的方式很多,java的有Jackson、Gson、FastJson等,Gson是谷歌提供的一款开源解析和生成json的库。


1、Gson实例化方法


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

第二种初始化方法,可以选择更多的序列化与反序列化方式,下面会详细介绍。


2、Gson基本用法


gson主要提供了fromJson和toJson两个方法,fromJson用于反序列化,toJson用于把json序列化为Json字符串。


public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
}
public String toJson(Object src) {
}


fromJson()第二个入参是反序列化成的对象类型


3、简单对象与Json的转换


class Person{
    private String name;
    private  int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public static void main(String[] args) {
        Person person = new Person("steven", 18);
        Gson gson = new Gson();
        String json = gson.toJson(person);
        System.out.println(json);
        String personJson = "{\"name\":\"steven\",\"age\":18}";
        Person person1 = gson.fromJson(personJson, Person.class);
        System.out.println(person1);
}
输出--》
{"name":"steven","age":18}
Person{name='steven', age=18}

可以看出Gson的强悍之处,普通的类库序列化和反序列时必须要求所对应的类中属性必须含有setter和getter方法,Gson完全不需要。


4、Map和Json的转换


public static void main(String[] args) {
        Person person = new Person("steven", 18);
        Map<String, Person> personMap = new HashMap<>();
        personMap.put("person", person);
        Gson gson = new Gson();
        String json = gson.toJson(personMap);
        System.out.println(json);
        String personJson = "{\"person\":{\"name\":\"steven\",\"age\":18}}";
        Map map = gson.fromJson(personJson, Map.class);
        System.out.println(map);
    }
输出--》
{"person":{"name":"steven","age":18}}
{person={name=steven, age=18.0}}

此处可以看出通过gson可以近乎完美的转换map和json,可以看出有个有小问题fromJson时,数字类型的value转换时会转成double类型,会把18转成18.0,下文会有解决方案。


三、Gson注解


1、序列化名注解@SerializedName


@SerializedName("personName")
private String name;
public static void main(String[] args) {
  Person person = new Person("steven", 18);
  Gson gson = new Gson();
  String json = gson.toJson(person);
  System.out.println(json);
}
输出--》
{"personName":"steven","age":18}


2、暴露序列化注解@Expose


使用此注解时就可以选择性的序列化类的属性,前面介绍的方法都是直接使用new Gson(),toJson()和fromJson()方法,这会将全部的字段序列化或反序列化,但实际中,有时我们并不需要全部字段序列化。或者随着项目的发展,版本可能会升级,某些实体类里可能会新增几个字段,这时版本不同数据不同,即低版本不能解析新的json数据(因为新的数据还有新增的字段)等。将对象序列化,默认情况下@Expose注解是不起作用的,需要用GsonBuilder创建Gson的时候调用了

GsonBuilder.excludeFieldsWithoutExposeAnnotation()方法。


加上注解等同于: @Expose(serialize = true,deserialize = true) 不加注解等同于: @Expose(serialize = false,deserialize = false)


@Expose(serialize = false,deserialize = true)
private String name;
@Expose(serialize = true,deserialize = true)
private int age;
public static void main(String[] args) {
   Person person = new Person("steven", 18);
   Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
   System.out.println(json);
   String personJson = "{\"name\":\"steven\",\"age\":18}";
   Person person1 = gson.fromJson(personJson, Person.class);
  System.out.println(person1);
}
输出--》
{"age":18}
Person{name='steven', age=18}


3、版本序列化注解@Since和@Util


使用注解表示哪些字段是哪个版本的,@Since(1.0)代表1.0版本,应用版本比它高或同等时会被序列化,反之不会,也可以用@Until(1.0)


@Since(2.0)
private String name;
@Since(1.0)
private int age;
public static void main(String[] args) {
        Person person = new Person("steven", 18);
        Gson gson = new GsonBuilder().setVersion(1.0).create();
        String json = gson.toJson(person);
        System.out.println(json);
        String personJson = "{\"name\":\"steven\",\"age\":18}";
        Person person1 = gson.fromJson(personJson, Person.class);
        System.out.println(person1);
}
输出--》
{"age":18}
Person{name='null', age=18}


四、Gson高阶用法


1、泛型类反序列化


fromJson时使用TypeToken格式


public static void main(String[] args) {
        Gson gson = new Gson();
        String json = "{\"person\":{\"name\":\"steven\",\"age\":18}}";
        Map<String, Person> personMap = gson.fromJson(json, new TypeToken<Map<String, Person>>() {
        }.getType());
        System.out.println(personMap);
}


2、Map复杂类型的key


使用new GsonBuilder().enableComplexMapKeySerialization().create();

//不加enableComplexMapKeySerialization时会默认调用key的toString()方法,而不是转Json
public static void main(String[] args) {
        Gson gson = new GsonBuilder().create();
        Map<Object, Object> map = new HashMap<>();
        Person person = new Person("steven", 17);
        map.put(person,"steven");
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"Person{name\u003d\u0027steven\u0027, age\u003d17}":"steven"}
//加上之后:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
        Map<Object, Object> map = new HashMap<>();
        Person person = new Person("steven", 17);
        map.put(person,"steven");
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
[[{"name":"steven","age":17},"steven"]]


3、特殊字符的处理


使用disableHtmlEscaping解决特殊字符编码问题

//不加disableHtmlEscaping时,等号,引号都会转码:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi","subnetwork=1500,meid=3200");
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"moi":"subnetwork\u003d1500,meid\u003d3200"}
//加上disableHtmlEscaping时:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().disableHtmlEscaping().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi","subnetwork=1500,meid=3200");
        String json = gson.toJson(map);
        System.out.println(json);
 }
 输出--》
{"moi":"subnetwork=1500,meid=3200"}


4、NULL值处理


Gson默认不会转换为null的属性,使用serializeNulls时不会丢失null属性

//不加serializeNulls会丢弃掉null值的属性:
 public static void main(String[] args) {
        Gson gson = new GsonBuilder().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi", "1500");
        map.put("name", null);
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"moi":"1500"}
//加上serializeNulls:
public static void main(String[] args) {
        Gson gson = new GsonBuilder().serializeNulls().create();
        Map<Object, Object> map = new HashMap<>();
        map.put("moi", "1500");
        map.put("name", null);
        String json = gson.toJson(map);
        System.out.println(json);
}
输出--》
{"moi":"1500","name":null}


5、数字类型处理


如前面所提到的一点,数字类型转换时,会出现精度问题,如下所示:

public static void main(String[] args) {
        Gson gson = new GsonBuilder().serializeNulls().create();
        String json = "{\"moi\":\"1500\",\"age\":18}";
        Map<Object, Object> map = gson.fromJson(json,new TypeToken<Map<Object, Object>>(){}.getType());
        System.out.println(map);
}
输出--》
{moi=1500, age=18.0}


Gson根据待解析的类型定位到具体的TypeAdaptor类,其接口的主要方法如下:


public abstract class TypeAdapter<T> {
  /**
   * Writes one JSON value (an array, object, string, number, boolean or null)
   * for {@code value}.
   *
   * @param value the Java object to write. May be null.
   */
  public abstract void write(JsonWriter out, T value) throws IOException;
  /**
   * Reads one JSON value (an array, object, string, number, boolean or null)
   * and converts it to a Java object. Returns the converted object.
   *
   * @return the converted Java object. May be null.
   */
  public abstract T read(JsonReader in) throws IOException;
}

通过read方法从JsonReader中读取相应的数据组装成最终的对象,由于Map中的字段的声明类型是Object,最终Gson会定位到内置的ObjectTypeAdaptor类,我们来分析一下该类的逻辑过程。


public final class ObjectTypeAdapter extends TypeAdapter<Object> {
  public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @SuppressWarnings("unchecked")
    @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
      if (type.getRawType() == Object.class) {
        return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
      }
      return null;
    }
  };
  private final Gson gson;
  ObjectTypeAdapter(Gson gson) {
    this.gson = gson;
  }
  @Override public Object read(JsonReader in) throws IOException {
    JsonToken token = in.peek();
    switch (token) {
    case BEGIN_ARRAY:
      List<Object> list = new ArrayList<Object>();
      in.beginArray();
      while (in.hasNext()) {
        list.add(read(in));
      }
      in.endArray();
      return list;
    case BEGIN_OBJECT:
      Map<String, Object> map = new LinkedTreeMap<String, Object>();
      in.beginObject();
      while (in.hasNext()) {
        map.put(in.nextName(), read(in));
      }
      in.endObject();
      return map;
    case STRING:
      return in.nextString();
      //此处可以看出,所有数值类型都转换为了Double类型
    case NUMBER:
      return in.nextDouble();
    case BOOLEAN:
      return in.nextBoolean();
    case NULL:
      in.nextNull();
      return null;
    default:
      throw new IllegalStateException();
    }
  }
  @SuppressWarnings("unchecked")
  @Override public void write(JsonWriter out, Object value) throws IOException {
    if (value == null) {
      out.nullValue();
      return;
    }
    TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
    if (typeAdapter instanceof ObjectTypeAdapter) {
      out.beginObject();
      out.endObject();
      return;
    }
    typeAdapter.write(out, value);
  }
}

看到该逻辑过程,如果Json对应的是Object类型,最终会解析为Map<String, Object>类型;其中Object类型跟Json中具体的值有关,比如双引号的""值翻译为STRING。可以看到数值类型(NUMBER)全部转换为了Double类型,所以就有了之前的问题,整型数据被转换为了Double类型,比如18变为了18.0。 所以想在不改变源码的基础上,实现数值类型的正确转换,需要新增一个适配器。


public class MyTypeAdapter extends TypeAdapter<Object> {
    @Override
    public Object read(JsonReader in) throws IOException {
        JsonToken token = in.peek();
        switch (token) {
            case BEGIN_ARRAY:
                List<Object> list = new ArrayList<Object>();
                in.beginArray();
                while (in.hasNext()) {
                    list.add(read(in));
                }
                in.endArray();
                return list;
            case BEGIN_OBJECT:
                Map<String, Object> map = new HashMap<String, Object>();
                in.beginObject();
                while (in.hasNext()) {
                    map.put(in.nextName(), read(in));
                }
                in.endObject();
                return map;
            case STRING:
                return in.nextString();
            case NUMBER:
                //将其作为一个字符串读取出来
                String numberStr = in.nextString();
                //返回的numberStr不会为null
                if (numberStr.contains(".") || numberStr.contains("e")
                        || numberStr.contains("E")) {
                    return Double.parseDouble(numberStr);
                }
                return Long.parseLong(numberStr);
            case BOOLEAN:
                return in.nextBoolean();
            case NULL:
                in.nextNull();
                return null;
            default:
                throw new IllegalStateException();
        }
    }
    @Override
    public void write(JsonWriter out, Object value) throws IOException {
        // 序列化无需实现
    }
}

此时就可以实现浮点型数值按double类型转换,整数型按Long型转换。另外一点可以看出当类型为BEGIN_OBJECT时ObjectTypeAdapter返回的Gson自定义的map类型LinkedTreeMap,如果使用时用到强转为HashMap会报错,由于我们使用的都是HashMap,所以自定义SonTypeAdapter复写成了HashMap。


五、总结


Gson是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。Gson核心jar包不到1M,非常精简,但提供的功能无疑是非常强大的,如果使用JDK自带的JSON解析API,使用起来相对比较繁琐一点,而且代码量较多,推荐大家可以尝试使用。


相关文章
|
Java 编译器 Android开发
Java compiler error: constant string too long
Java compiler error: constant string too long
UPC-Simple String Queries(二维树状数组)
UPC-Simple String Queries(二维树状数组)
68 0
Data Structures and Algorithms (English) - 7-12 How Long Does It Take(25 分)
Data Structures and Algorithms (English) - 7-12 How Long Does It Take(25 分)
89 0
|
前端开发
Google Earth Engine——Layer error: Description length exceeds maximum.解决办法
Google Earth Engine——Layer error: Description length exceeds maximum.解决办法
586 0
Google Earth Engine——Layer error: Description length exceeds maximum.解决办法
SAP UI5 Opportunity type long description empty issue
Created by Wang, Jerry, last modified on Mar 03, 2016
94 0
SAP UI5 Opportunity type long description empty issue
the code place where the binding is converted to final value displayed in ui
the code place where the binding is converted to final value displayed in ui
the code place where the binding is converted to final value displayed in ui
RxJava/RxAndroid:ConnectableObservable &amp; replay(int bufferSize, long time, TimeUnit unit)
RxJava/RxAndroid:ConnectableObservable & replay(int bufferSize, long time, TimeUnit unit) import android.
1040 0
|
Android开发
RxJava/RxAndroid:ConnectableObservable &amp; replay(long time, TimeUnit unit)
RxJava/RxAndroid:ConnectableObservable & replay(long time, TimeUnit unit) import android.
1081 0