Gson是一个Java库,可用于将Java对象转换为它们的JSON表示。它还可以用于将JSON字符串转换为等效的Java对象。Gson可以处理任意Java对象,包括您没有源代码的已有对象。

一、简单使用

1. 导入

在Android的build.gradle中添加依赖:

  1. dependencies {
  2. implementation 'com.google.code.gson:gson:2.10.1'
  3. }

2. Gson序列化

下面以一个简单例子展示对象序列化:

  1. Item item = new Item();
  2. item.id = 101;
  3. item.name = "Apple";
  4. Gson gson = new Gson();
  5. String json = gson.toJson(item);
  6. Log.d(TAG, json);

打印的json字符串值为:

  1. {"id": 101,"name": "Apple"}

序列化就是如此简单,json字段名就是类的属性名,如果要修改序列化字段名,需要在类型属性上添加注解@SerializedName

  1. public class Item {
  2. @SerializedName("ID")
  3. int id;
  4. String name;
  5. @NonNull
  6. @Override
  7. public String toString() {
  8. return "Item{" +
  9. "id=" + id +
  10. ", name='" + name + '\'' +
  11. '}';
  12. }
  13. }

那么json的字符串值将变为:

  1. {"ID": 101,"name": "Apple"}

3. Gson对象解析

将上面的json直接解析为Item对象:

  1. Item it = gson.fromJson(json, Item.class);
  2. Log.d(TAG, it.toString());

打印结果如下:

  1. Item{id=101, name='Apple'}

可见Gson的使用是非常简单的,掌握上述方法已经可以处理大部分JSON序列化问题了。

二、混淆

Gson的对象解析利用到了java反射机制,如果开启了混淆是否影响序列化和反序列化,接下来做一个简单的实验。

在build.gradle中打开混淆开关:

  1. buildTypes {
  2. debug {
  3. minifyEnabled true
  4. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  5. }
  6. }

再次运行上面的代码,将得到如下类似打印:

  1. {"ID":101,"b":"Apple"}

由于在id字段上添加了注解序列化名称,因此ID名称并没有因混淆被影响;而name字段由于混淆名称变为了b,所以JSON字段名称也变成了b

所以要避免混淆对系列化的影响,一是可以在需要化字段上添加注解@SerializedName,二是防止整个类被混淆,如可在类上添加注解@Keep,或者在混淆文件中添加

  1. -keepclasseswithmembernames class com.ihuntto.hellogson.Item {*;}

三、序列化部分类属性

上面给出的例子是对类的属性进行全部JSON序列化,如果要进行部分序列化,该怎么办?这里有多种方式可选择。

1. 使用transient修饰符

在不需要序列化字段前面添加transient

  1. transient String nickName = "Pie";

2. 指定Modifier字段

如不序列化privateprotected字段:

  1. Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.PRIVATE, Modifier.PROTECTED).create();

当然还用其他Modifier可以选择:

  1. package java.lang.reflect;
  2. public class Modifier {
  3. public static final int ABSTRACT = 1024;
  4. public static final int FINAL = 16;
  5. public static final int INTERFACE = 512;
  6. public static final int NATIVE = 256;
  7. public static final int PRIVATE = 2;
  8. public static final int PROTECTED = 4;
  9. public static final int PUBLIC = 1;
  10. public static final int STATIC = 8;
  11. public static final int STRICT = 2048;
  12. public static final int SYNCHRONIZED = 32;
  13. public static final int TRANSIENT = 128;
  14. public static final int VOLATILE = 64;
  15. ...
  16. }

3. 使用注解@Expose

在需要进行序列化的字段上添加@Expose注解:

  1. public class Item {
  2. @Expose
  3. @SerializedName("ID")
  4. int id;
  5. @Expose
  6. String name;
  7. transient String nickName = "Pie";
  8. String otherStuff;
  9. ...
  10. }

使用GsonBuilder创建Gson,以排除不包含@Expose注解的字段:

  1. Item item = new Item();
  2. item.id = 101;
  3. item.name = "Apple";
  4. item.otherStuff = "Red";
  5. Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
  6. String json = gson.toJson(item);
  7. Log.d(TAG, json);

序列化的json字符串打印为:

  1. {"ID":101,"name":"Apple"}

可见nickNameotherStuff字段都没有被序列化。

也可以指定字段只支持序列化或反序列化:

  1. @Expose(serialize = true, deserialize = false)

4. 自定义排除策略

如果上面还不能满足部分字段序列化需求,还可以自定义排除策略:

  1. public class MyExclusionStrategy implements ExclusionStrategy {
  2. @Override
  3. public boolean shouldSkipField(FieldAttributes field) {
  4. return field.getName().startsWith("_");
  5. }
  6. @Override
  7. public boolean shouldSkipClass(Class<?> clazz) {
  8. return clazz.isAnonymousClass();
  9. }
  10. }

排除以下划线开头的字段,以及匿名内部类不进行序列化,还可以根据FieldAttributesClass<?>支持的判断添加排除策略。

综合上述4种方法,首先不推荐使用@Expose注解,因为任何库中类的字段不支持你添加@Expose注解的,也就是你不能序列化任何库中的类对象;其次不推荐使用Modifier进行排除序列化字段,一是代码规范基本建议不使用public字段,二是protectedprivate可以用来区别子类的可见性,但不是区分序列化的标志,如果实在是要使用,建议使用其他Modifier来区分;比较建议使用transient,因为java语言已经明确其修饰字段不进行序列化。

四、反序列化含子类的列表

如果在ArrayList<IShape>的列表中添加了IShape的子类,那么这个列表还能正常序列化和反序列化吗?

  1. interface IShape {
  2. void draw();
  3. }
  1. public class Circle implements IShape {
  2. private static final String TAG = Circle.class.getSimpleName();
  3. float x;
  4. float y;
  5. float radius;
  6. public Circle() {
  7. }
  8. public Circle(float x, float y, float radius) {
  9. this.x = x;
  10. this.y = y;
  11. this.radius = radius;
  12. }
  13. @Override
  14. public void draw() {
  15. Log.d(TAG, "draw " + toString());
  16. }
  17. @NonNull
  18. @Override
  19. public String toString() {
  20. return "Circle{" +
  21. "x=" + x +
  22. ", y=" + y +
  23. ", radius=" + radius +
  24. '}';
  25. }
  26. }
  1. public class Rectangle implements IShape {
  2. private static final String TAG = Rectangle.class.getSimpleName();
  3. float x;
  4. float y;
  5. float width;
  6. float height;
  7. public Rectangle() {
  8. }
  9. public Rectangle(float x, float y, float width, float height) {
  10. this.x = x;
  11. this.y = y;
  12. this.width = width;
  13. this.height = height;
  14. }
  15. @Override
  16. public void draw() {
  17. Log.d(TAG, "draw " + toString());
  18. }
  19. @NonNull
  20. @Override
  21. public String toString() {
  22. return "Rectangle{" +
  23. "x=" + x +
  24. ", y=" + y +
  25. ", width=" + width +
  26. ", height=" + height +
  27. '}';
  28. }
  29. }
  1. public class ShapeHolder {
  2. List<IShape> shapes = new ArrayList<>();
  3. }
  1. IShape circle = new Circle(0, 0, 10);
  2. IShape rectangle = new Rectangle(0, 0, 20, 10);
  3. ShapeHolder holder = new ShapeHolder();
  4. holder.shapes.add(circle);
  5. holder.shapes.add(rectangle);
  6. Gson gson = new Gson();
  7. String json = gson.toJson(holder);
  8. Log.d(TAG, json);

上述代码可以正常序列化:

  1. {"shapes":[{"radius":10.0,"x":0.0,"y":0.0},{"height":10.0,"width":20.0,"x":0.0,"y":0.0}]}

但是将json字符串进行反序列化时:

  1. ShapeHolder shapeHolder = gson.fromJson(json, ShapeHolder.class);

将得到如下异常:

  1. com.google.gson.JsonIOException: Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for this type. Interface name: com.ihuntto.hellogson.IShape

异常提示注册一个InstanceCreator或者TypeAdapter,那么尝试为gson添加一个TypeAdapter,首先在CircleRectangle中添加type字段以区分不同的序列化类:

  1. public class Circle implements IShape {
  2. ...
  3. String type = "circle";
  4. ...
  5. }
  1. public class Rectangle implements IShape {
  2. ...
  3. String type = "rectangle";
  4. ...
  5. }
  1. public class IShapeDeserializer implements JsonDeserializer<IShape> {
  2. private final Gson mGson = new Gson();
  3. private final HashMap<String, Class<? extends IShape>> mTypeMap = new HashMap<>();
  4. public IShapeDeserializer() {
  5. mTypeMap.put("circle", Circle.class);
  6. mTypeMap.put("rectangle", Rectangle.class);
  7. }
  8. @Override
  9. public IShape deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
  10. Class<? extends IShape> clazz = mTypeMap.get(json.getAsJsonObject().get("type").getAsString());
  11. return mGson.fromJson(json, clazz);
  12. }
  13. }

现在进行序列化与反序列化:

  1. IShape circle = new Circle(0, 0, 10);
  2. IShape rectangle = new Rectangle(0, 0, 20, 10);
  3. ShapeHolder holder = new ShapeHolder();
  4. holder.shapes.add(circle);
  5. holder.shapes.add(rectangle);
  6. Gson gson = new GsonBuilder().registerTypeAdapter(IShape.class, new IShapeDeserializer()).create();
  7. String json = gson.toJson(holder);
  8. Log.d(TAG, json);
  9. ShapeHolder shapeHolder = gson.fromJson(json, ShapeHolder.class);
  10. for (IShape shape : shapeHolder.shapes) {
  11. shape.draw();
  12. }

运行上述代码将得到如下打印:

  1. 2023-12-17 12:37:22.000 31010-31010/com.ihuntto.hellogson D/MainActivity: {"shapes":[{"radius":10.0,"type":"circle","x":0.0,"y":0.0},{"height":10.0,"type":"rectangle","width":20.0,"x":0.0,"y":0.0}]}
  2. 2023-12-17 12:37:22.006 31010-31010/com.ihuntto.hellogson D/Circle: draw Circle{x=0.0, y=0.0, radius=10.0}
  3. 2023-12-17 12:37:22.006 31010-31010/com.ihuntto.hellogson D/Rectangle: draw Rectangle{x=0.0, y=0.0, width=20.0, height=10.0}

如果我定义了一个ShapeCompounds

  1. public class ShapeCompounds implements IShape {
  2. private static final String TAG = ShapeCompounds.class.getSimpleName();
  3. String type = "shape-compounds";
  4. List<IShape> shapes = new ArrayList<>();
  5. @Override
  6. public void draw() {
  7. Log.d(TAG, "draw " + toString());
  8. }
  9. @NonNull
  10. @Override
  11. public String toString() {
  12. return "ShapeCompounds{" +
  13. "type='" + type + '\'' +
  14. ", shapes=" + shapes +
  15. '}';
  16. }
  17. }

并且在IShapeDeserializer进行注册:

  1. public class IShapeDeserializer implements JsonDeserializer<IShape> {
  2. ...
  3. public IShapeDeserializer() {
  4. ...
  5. mTypeMap.put("shape-compounds", ShapeCompounds.class);
  6. }
  7. ...
  8. }

那么下面这段代码还能运行正常吗?

  1. IShape circle = new Circle(0, 0, 10);
  2. IShape rectangle = new Rectangle(0, 0, 20, 10);
  3. ShapeCompounds compounds = new ShapeCompounds();
  4. compounds.shapes.add(circle);
  5. compounds.shapes.add(rectangle);
  6. ShapeHolder holder = new ShapeHolder();
  7. holder.shapes.add(circle);
  8. holder.shapes.add(rectangle);
  9. holder.shapes.add(compounds);
  10. Gson gson = new GsonBuilder().registerTypeAdapter(IShape.class, new IShapeDeserializer()).create();
  11. String json = gson.toJson(holder);
  12. Log.d(TAG, json);
  13. ShapeHolder shapeHolder = gson.fromJson(json, ShapeHolder.class);
  14. for (IShape shape : shapeHolder.shapes) {
  15. shape.draw();
  16. }

很不辛,上述代码可以序列化成功,但反序列化失败,如果在IShapeDeserializerdeserialize方法出加上打印可以知道原因是无法反序列化ShapeCompounds,因为序列化ShapeCompoundsGson为普通Gson,并没有注册TypeAdapter,因此同样需要为IShapeDeserializer注册TypeAdapter

  1. public class IShapeDeserializer implements JsonDeserializer<IShape> {
  2. private static Gson sInstance;
  3. private final HashMap<String, Class<? extends IShape>> mTypeMap = new HashMap<>();
  4. private IShapeDeserializer() {
  5. mTypeMap.put("circle", Circle.class);
  6. mTypeMap.put("rectangle", Rectangle.class);
  7. mTypeMap.put("shape-compounds", ShapeCompounds.class);
  8. }
  9. @Override
  10. public IShape deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
  11. Class<? extends IShape> clazz = mTypeMap.get(json.getAsJsonObject().get("type").getAsString());
  12. return sInstance.fromJson(json, clazz);
  13. }
  14. public static Gson crate() {
  15. if (sInstance == null) {
  16. sInstance = new GsonBuilder().registerTypeAdapter(IShape.class, new IShapeDeserializer()).create();
  17. }
  18. return sInstance;
  19. }
  20. }

将测试代码中的gson对象创建方法替换为:

  1. Gson gson = IShapeDeserializer.crate();

运行测试代码就可以得到正常打印了:

  1. 2023-12-17 12:54:41.038 31506-31506/com.ihuntto.hellogson D/MainActivity: {"shapes":[{"radius":10.0,"type":"circle","x":0.0,"y":0.0},{"height":10.0,"type":"rectangle","width":20.0,"x":0.0,"y":0.0},{"shapes":[{"radius":10.0,"type":"circle","x":0.0,"y":0.0},{"height":10.0,"type":"rectangle","width":20.0,"x":0.0,"y":0.0}],"type":"shape-compounds"}]}
  2. 2023-12-17 12:54:41.044 31506-31506/com.ihuntto.hellogson D/Circle: draw Circle{x=0.0, y=0.0, radius=10.0}
  3. 2023-12-17 12:54:41.044 31506-31506/com.ihuntto.hellogson D/Rectangle: draw Rectangle{x=0.0, y=0.0, width=20.0, height=10.0}
  4. 2023-12-17 12:54:41.045 31506-31506/com.ihuntto.hellogson D/ShapeCompounds: draw ShapeCompounds{type='shape-compounds', shapes=[Circle{x=0.0, y=0.0, radius=10.0}, Rectangle{x=0.0, y=0.0, width=20.0, height=10.0}]}

这里仅总结我在使用Gson过程中遇到的问题及解决的方法,后续遇到新的问题再进行更新。

参考

  1. Gson
  2. Gson工具的使用、序列化/反序列化集合、对象、遇到的问题
  3. Gson反序列化 子类、接口实现类

Android中使用Gson的更多相关文章

  1. Android中使用Gson解析JSON数据的两种方法

    Json是一种类似于XML的通用数据交换格式,具有比XML更高的传输效率;本文将介绍两种方法解析JSON数据,需要的朋友可以参考下   Json是一种类似于XML的通用数据交换格式,具有比XML更高的 ...

  2. Android中使用Gson解析JSON数据

      Android中使用Gson解析JSON数据 在Android中可以使用Gson解析JSON数据 首先,从 code.google.com/p/google-gson/downloads/list ...

  3. Android中Retrifit使用总结

    Android中网络请求框架Retrofit的使用注意事项 1.Retrofit是基于OkHttp网络请求框架的二次封装而已,懂Okhttp的小伙伴,那么Retrofit也就基本都会. 2.Retro ...

  4. Android 中的Json解析工具fastjson 、序列化、反序列化

    Android中通常需要访问服务器,然而服务器返回的数据很多时候都是Json格式 1.fastjson简介 阿里巴巴FastJson是一个Json处理工具包,包括“序列化”和“反序列化”两部分,它具备 ...

  5. Android 中的缓存机制与实现

    Android开发本质上就是手机和互联网中的web服务器之间进行通信,就必然需要从服务端获取数据,而反复通过网络获取数据是比较耗时的,特别是访问比较多的时候,会极大影响了性能,Android中可通过二 ...

  6. Android开源库--Gson谷歌官方json解析库

    官方文档地址:http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/index.html 官方网站:http://code.go ...

  7. Android Volley和Gson实现网络数据加载

    Android Volley和Gson实现网络数据加载 先看接口 1 升级接口 http://s.meibeike.com/mcloud/ota/cloudService POST请求 参数列表如下 ...

  8. Android 中解析 JSON

    有什么不懂的可以去官网去看看:www.json.org 在google android中也有关于解析JSON的类库:JsonReader,但是只能在3.0以后的版本中才可以用,在这里我们用google ...

  9. 1.Android中解析json程序代码

    Android程序解析json数据可以通过gson的方式,这种情况需要导入相应的jar包.测试代码如下: @Override protected void onCreate(Bundle savedI ...

  10. Android探究之Gson@SerializedName

    @SerializedName注解的意义 当我们使用Gson解析Json数据时都会创建一个对应实体类,有时候Json数据里面的字段是Java关键词或者Json数据里面的字段太简单,我们想在实体类中自定 ...

随机推荐

  1. 聊聊数据库事务内嵌TCP连接

    最近再看项目代码,发现很多的service里面,喜欢在事务内部再去调用HTTP请求,简单分析下此种方式的利弊与解决策略. 概述 在数据库内部嵌套TCP连接(一般是HTTP调用或是RPC远程调用). @ ...

  2. doris建表报错 errCode = 2, detailMessage = Scale of decimal must between 0 and 9. Scale was set to: 10

    doris建表报错 问题背景 当我从Mpp库向doris库中导数据时,需要先创建对应的数据表,将Mpp库中表的建表语句略作修改后,在doris服务器上运行 CREATE TABLE opt_conne ...

  3. 全网最详细Java-JVM

    Java-JVM ①JVM概述 ❶基本介绍 JVM:全称 Java Virtual Machine,一个虚拟计算机,Java 程序的运行环境(Java二进制字节码的运行环境) 特点: Java 虚拟机 ...

  4. MFC中使用函数实现ini文件的连续读写

    实现的思路: 首先通过读取文件中的count值,确定当前信息条数: 第二步:将count进行累加,把信息写到累加后的键值"="的后面: 第三步:写入count累加值,实现连续读写: ...

  5. JS深入学习笔记 - 第三章.变量作用域与内存

    1.原始值和引用值 ECMScript变量包含两种不同类型是数据:原始值和引用值. 原始值:最简单的数据.有6中原始值:Undefined.Null.Boolean.Number.String和Sym ...

  6. 汇编debug的安装

    实验一查看CPU和内存,用机器指令和汇编指令编程 在做实验前需要debug命令. 工具:dosbox,debug.exe 安装:dosbox :https://www.dosbox.com/ debu ...

  7. 研发三维GIS系统笔记/实现wgs84投影-002

    四叉树代码修改完善 原来的代码中,没有使用投影转换,直接使用的是世界坐标(单位是米), CELLQuadTree::CELLQuadTree( CELLTerrainInterface* pInter ...

  8. CF451B

    题目简化和分析: 这题就是判断将一段翻转后是否能变为升序的数组. 我的方法是保存原数组每一个数出现的位置(相同任意一个),让后另外用一个数组存储排好序后的原数组,逐一进行比较. 若同,则跳到下一个元素 ...

  9. Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法

    Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,此处测试代码如下,这里使用add方法: 1 public class main { 2 public static vo ...

  10. 2023-10-25:用go语言,假如某公司目前推出了N个在售的金融产品(1<=N<=100) 对于张三,用ai表示他购买了ai(0<=ai<=10^4)份额的第i个产品(1<=i<=N) 现给出K(

    2023-10-25:用go语言,假如某公司目前推出了N个在售的金融产品(1<=N<=100) 对于张三,用ai表示他购买了ai(0<=ai<=10^4)份额的第i个产品(1& ...