Life is short, you need Gson
一、JSON简介
JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
JSON 键值对是用来保存JS对象的一种方式,和JS对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值,如下例子所示:
{“firstName”: “John”}
二、谷歌Gson
解析和生成json的方式很多,java的有Jackson、Gson、FastJson等,Gson是谷歌提供的一款开源解析和生成json的库。
Gson gson = new Gson();
Gson gson = new GsonBuilder().create();
第二种初始化方法,可以选择更多的序列化与反序列化方式,下面会详细介绍。
gson主要提供了fromJson和toJson两个方法,fromJson用于反序列化,toJson用于把json序列化为Json字符串。
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {}public String toJson(Object src) {}
fromJson()第二个入参是反序列化成的对象类型
class Person{private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic 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完全不需要。
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}
使用注解表示哪些字段是哪个版本的,@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高阶用法
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);}
使用new GsonBuilder().enableComplexMapKeySerialization().create();
//不加enableComplexMapKeySerialization时会默认调用key的toString()方法,而不是转Jsonpublic 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"]]
使用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"}
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}
如前面所提到的一点,数字类型转换时,会出现精度问题,如下所示:
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 类型;其中Object类型跟Json中具体的值有关,比如双引号的””值翻译为STRING。可以看到数值类型(NUMBER)全部转换为了Double类型,所以就有了之前的问题,整型数据被转换为了Double类型,比如18变为了18.0。
所以想在不改变源码的基础上,实现数值类型的正确转换,需要新增一个适配器。
public class MyTypeAdapter extends TypeAdapter<Object> {@Overridepublic 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不会为nullif (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();}}@Overridepublic 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,使用起来相对比较繁琐一点,而且代码量较多,推荐大家可以尝试使用。
本文详细介绍了谷歌的Gson库,包括JSON的简介,Gson的基本使用,注解特性以及高阶用法。Gson提供fromJson和toJson方法进行序列化和反序列化,支持普通对象、Map与Json的转换。文章还讨论了如何处理特殊字符、NULL值,以及解决数字类型转换精度问题的策略。
1467

被折叠的 条评论
为什么被折叠?



