人生苦短,我用Gson

本文详细介绍了谷歌的Gson库,包括JSON的简介,Gson的基本使用,注解特性以及高阶用法。Gson提供fromJson和toJson方法进行序列化和反序列化,支持普通对象、Map与Json的转换。文章还讨论了如何处理特殊字符、NULL值,以及解决数字类型转换精度问题的策略。

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的库。

1、Gson实例化方法

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

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

2、Gson基本用法

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

 
  1. public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
  2.  
  3. }
  4. public String toJson(Object src) {
  5.  
  6. }

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

3、简单对象与Json的转换

 
  1. class Person{
  2. private String name;
  3. private int age;
  4.  
  5. public Person(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9.  
  10. @Override
  11. public String toString() {
  12. return "Person{" +
  13. "name='" + name + '\'' +
  14. ", age=" + age +
  15. '}';
  16. }
  17. }
  18.  
  19.  
  20. public static void main(String[] args) {
  21. Person person = new Person("steven", 18);
  22. Gson gson = new Gson();
  23. String json = gson.toJson(person);
  24. System.out.println(json);
  25. String personJson = "{\"name\":\"steven\",\"age\":18}";
  26. Person person1 = gson.fromJson(personJson, Person.class);
  27. System.out.println(person1);
  28. }
  29. 输出--》
  30. {"name":"steven","age":18}
  31. Person{name='steven', age=18}

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

4、Map和Json的转换

 
  1. public static void main(String[] args) {
  2. Person person = new Person("steven", 18);
  3. Map<String, Person> personMap = new HashMap<>();
  4. personMap.put("person", person);
  5. Gson gson = new Gson();
  6. String json = gson.toJson(personMap);
  7. System.out.println(json);
  8. String personJson = "{\"person\":{\"name\":\"steven\",\"age\":18} }";
  9. Map map = gson.fromJson(personJson, Map.class);
  10. System.out.println(map);
  11. }
  12. 输出--》
  13. {"person":{"name":"steven","age":18} }
  14. {person={name=steven, age=18.0} }

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

三、Gson注解

1、序列化名注解@SerializedName

 
  1. @SerializedName("personName")
  2. private String name;
  3.  
  4. public static void main(String[] args) {
  5. Person person = new Person("steven", 18);
  6. Gson gson = new Gson();
  7. String json = gson.toJson(person);
  8. System.out.println(json);
  9. }
  10. 输出--》
  11. {"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)

 
  1. @Expose(serialize = false,deserialize = true)
  2. private String name;
  3. @Expose(serialize = true,deserialize = true)
  4. private int age;
  5.  
  6. public static void main(String[] args) {
  7. Person person = new Person("steven", 18);
  8. Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
  9. System.out.println(json);
  10. String personJson = "{\"name\":\"steven\",\"age\":18}";
  11. Person person1 = gson.fromJson(personJson, Person.class);
  12. System.out.println(person1);
  13. }
  14. 输出--》
  15. {"age":18}
  16. Person{name='steven', age=18}

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

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

 
  1. @Since(2.0)
  2. private String name;
  3. @Since(1.0)
  4. private int age;
  5.  
  6.  
  7. public static void main(String[] args) {
  8. Person person = new Person("steven", 18);
  9. Gson gson = new GsonBuilder().setVersion(1.0).create();
  10. String json = gson.toJson(person);
  11. System.out.println(json);
  12. String personJson = "{\"name\":\"steven\",\"age\":18}";
  13. Person person1 = gson.fromJson(personJson, Person.class);
  14. System.out.println(person1);
  15. }
  16.  
  17. 输出--》
  18. {"age":18}
  19. Person{name='null', age=18}

四、Gson高阶用法

1、泛型类反序列化

fromJson时使用TypeToken格式

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

2、Map复杂类型的key

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

 
  1. //不加enableComplexMapKeySerialization时会默认调用key的toString()方法,而不是转Json
  2. public static void main(String[] args) {
  3. Gson gson = new GsonBuilder().create();
  4. Map<Object, Object> map = new HashMap<>();
  5. Person person = new Person("steven", 17);
  6. map.put(person,"steven");
  7. String json = gson.toJson(map);
  8. System.out.println(json);
  9. }
  10. 输出--》
  11. {"Person{name\u003d\u0027steven\u0027, age\u003d17}":"steven"}
  12.  
  13.  
  14. //加上之后:
  15. public static void main(String[] args) {
  16. Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
  17. Map<Object, Object> map = new HashMap<>();
  18. Person person = new Person("steven", 17);
  19. map.put(person,"steven");
  20. String json = gson.toJson(map);
  21. System.out.println(json);
  22. }
  23. 输出--》
  24. [[{"name":"steven","age":17},"steven"]]

3、特殊字符的处理

使用disableHtmlEscaping接触特殊字符编码问题

 
  1. //不加disableHtmlEscaping时,等号,引号都会转码:
  2. public static void main(String[] args) {
  3. Gson gson = new GsonBuilder().disableHtmlEscaping().create();
  4. Map<Object, Object> map = new HashMap<>();
  5. map.put("moi","subnetwork=1500,meid=3200");
  6. String json = gson.toJson(map);
  7. System.out.println(json);
  8. }
  9. 输出--》
  10. {"moi":"subnetwork\u003d1500,meid\u003d3200"}
  11.  
  12. //加上disableHtmlEscaping时:
  13. public static void main(String[] args) {
  14. Gson gson = new GsonBuilder().disableHtmlEscaping().create();
  15. Map<Object, Object> map = new HashMap<>();
  16. map.put("moi","subnetwork=1500,meid=3200");
  17. String json = gson.toJson(map);
  18. System.out.println(json);
  19. }
  20. 输出--》
  21. {"moi":"subnetwork=1500,meid=3200"}

4、NULL值处理

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

 
  1. //不加serializeNulls会丢弃掉null值的属性:
  2. public static void main(String[] args) {
  3. Gson gson = new GsonBuilder().create();
  4. Map<Object, Object> map = new HashMap<>();
  5. map.put("moi", "1500");
  6. map.put("name", null);
  7. String json = gson.toJson(map);
  8. System.out.println(json);
  9. }
  10. 输出--》
  11. {"moi":"1500"}
  12.  
  13. //加上serializeNulls:
  14. public static void main(String[] args) {
  15. Gson gson = new GsonBuilder().serializeNulls().create();
  16. Map<Object, Object> map = new HashMap<>();
  17. map.put("moi", "1500");
  18. map.put("name", null);
  19. String json = gson.toJson(map);
  20. System.out.println(json);
  21. }
  22. 输出--》
  23. {"moi":"1500","name":null}

5、数字类型处理

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

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

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

 

 
  1. public abstract class TypeAdapter<T> {
  2.  
  3. /**
  4. * Writes one JSON value (an array, object, string, number, boolean or null)
  5. * for {@code value}.
  6. *
  7. * @param value the Java object to write. May be null.
  8. */
  9. public abstract void write(JsonWriter out, T value) throws IOException;
  10.  
  11.  
  12.  
  13. /**
  14. * Reads one JSON value (an array, object, string, number, boolean or null)
  15. * and converts it to a Java object. Returns the converted object.
  16. *
  17. * @return the converted Java object. May be null.
  18. */
  19. public abstract T read(JsonReader in) throws IOException;
  20. }

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

 
  1. public final class ObjectTypeAdapter extends TypeAdapter<Object> {
  2. public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
  3. @SuppressWarnings("unchecked")
  4. @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
  5. if (type.getRawType() == Object.class) {
  6. return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
  7. }
  8. return null;
  9. }
  10. };
  11.  
  12. private final Gson gson;
  13.  
  14. ObjectTypeAdapter(Gson gson) {
  15. this.gson = gson;
  16. }
  17.  
  18. @Override public Object read(JsonReader in) throws IOException {
  19. JsonToken token = in.peek();
  20. switch (token) {
  21. case BEGIN_ARRAY:
  22. List<Object> list = new ArrayList<Object>();
  23. in.beginArray();
  24. while (in.hasNext()) {
  25. list.add(read(in));
  26. }
  27. in.endArray();
  28. return list;
  29.  
  30. case BEGIN_OBJECT:
  31. Map<String, Object> map = new LinkedTreeMap<String, Object>();
  32. in.beginObject();
  33. while (in.hasNext()) {
  34. map.put(in.nextName(), read(in));
  35. }
  36. in.endObject();
  37. return map;
  38.  
  39. case STRING:
  40. return in.nextString();
  41. //此处可以看出,所有数值类型都转换为了Double类型
  42. case NUMBER:
  43. return in.nextDouble();
  44.  
  45. case BOOLEAN:
  46. return in.nextBoolean();
  47.  
  48. case NULL:
  49. in.nextNull();
  50. return null;
  51.  
  52. default:
  53. throw new IllegalStateException();
  54. }
  55. }
  56.  
  57. @SuppressWarnings("unchecked")
  58. @Override public void write(JsonWriter out, Object value) throws IOException {
  59. if (value == null) {
  60. out.nullValue();
  61. return;
  62. }
  63.  
  64. TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
  65. if (typeAdapter instanceof ObjectTypeAdapter) {
  66. out.beginObject();
  67. out.endObject();
  68. return;
  69. }
  70.  
  71. typeAdapter.write(out, value);
  72. }
  73. }

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

 
  1. public class MyTypeAdapter extends TypeAdapter<Object> {
  2.  
  3. @Override
  4. public Object read(JsonReader in) throws IOException {
  5. JsonToken token = in.peek();
  6. switch (token) {
  7. case BEGIN_ARRAY:
  8. List<Object> list = new ArrayList<Object>();
  9. in.beginArray();
  10. while (in.hasNext()) {
  11. list.add(read(in));
  12. }
  13. in.endArray();
  14. return list;
  15.  
  16. case BEGIN_OBJECT:
  17. Map<String, Object> map = new HashMap<String, Object>();
  18. in.beginObject();
  19. while (in.hasNext()) {
  20. map.put(in.nextName(), read(in));
  21. }
  22. in.endObject();
  23. return map;
  24.  
  25. case STRING:
  26. return in.nextString();
  27.  
  28. case NUMBER:
  29. //将其作为一个字符串读取出来
  30. String numberStr = in.nextString();
  31. //返回的numberStr不会为null
  32. if (numberStr.contains(".") || numberStr.contains("e")
  33. || numberStr.contains("E")) {
  34. return Double.parseDouble(numberStr);
  35. }
  36. return Long.parseLong(numberStr);
  37. case BOOLEAN:
  38. return in.nextBoolean();
  39.  
  40. case NULL:
  41. in.nextNull();
  42. return null;
  43.  
  44. default:
  45. throw new IllegalStateException();
  46. }
  47. }
  48.  
  49. @Override
  50. public void write(JsonWriter out, Object value) throws IOException {
  51. // 序列化无需实现
  52. }
  53. }

此时就可以实现浮点型数值按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 文件更加简洁高效,以下是完整的代码示例和最佳实践: --- ### 1. 定义数据类(Data Class) 首先创建与 JSON 结构匹配的 Kotlin 数据类: ```kotlin data class AppConfig( val bools: Bools, val dimens: Dimens ) { data class Bools( val overlay: Boolean, val change_seat_voice_layout: Boolean, val seat_view_voice_sonic_special: Boolean ) data class Dimens( val row_one_margin: Int, val row_two_margin: Int, val row_one_top: Int, val row_two_top: Int, val seat_voice_margin_top: Int, val seat_voice_padding_top: Int, val seat_voice_padding_left: Int, val seat_voice_padding_right: Int, val seat_voice_padding_bottom: Int ) } ``` --- ### 2. 读取并解析 JSON 文件 使用 Gson 从物理路径解析: ```kotlin import com.google.gson.Gson import java.io.File fun readConfigWithGson(filePath: String): AppConfig? { return try { val jsonString = File(filePath).readText() Gson().fromJson(jsonString, AppConfig::class.java) } catch (e: Exception) { e.printStackTrace() null } } ``` --- ### 3. 使用示例 ```kotlin // 调用示例 val config = readConfigWithGson("/sdcard/config.json")?.apply { // 访问解析后的数据 Log.d("GSON", "Overlay enabled: ${bools.overlay}") Log.d("GSON", "Row one margin: ${dimens.row_one_margin}dp") } // 处理可能的空值 config ?: showError("Failed to load config") ``` --- ### 4. 关键优化点 1. **自动类型转换** Gson 会自动将 JSON 中的 `boolean` 和 `number` 转换为 Kotlin 的 `Boolean`/`Int`。 2. **默认值处理** 在数据类中使用默认值避免空指针: ```kotlin data class Bools( val overlay: Boolean = false, // 默认false // 其他字段... ) ``` 3. **性能建议** 复用 Gson 实例避免重复创建: ```kotlin private val gson = Gson() // 全局单例 ``` 4. **复杂场景扩展** 如需自定义解析逻辑,可注册 `TypeAdapter`: ```kotlin val gson = GsonBuilder() .registerTypeAdapter(Dimens::class.java, DimensAdapter()) .create() ``` --- ### 5. 权限与路径兼容性 - **外部存储权限**:仍需声明 `READ_EXTERNAL_STORAGE` - **更安全路径**:推荐使用应用私有目录: ```kotlin val file = File(context.getExternalFilesDir(null), "config.json") ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值