Android中使用Gson
Gson是一个Java库,可用于将Java对象转换为它们的JSON表示。它还可以用于将JSON字符串转换为等效的Java对象。Gson可以处理任意Java对象,包括您没有源代码的已有对象。
一、简单使用
1. 导入
在Android的build.gradle中添加依赖:
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
}
2. Gson序列化
下面以一个简单例子展示对象序列化:
Item item = new Item();
item.id = 101;
item.name = "Apple";
Gson gson = new Gson();
String json = gson.toJson(item);
Log.d(TAG, json);
打印的json字符串值为:
{"id": 101,"name": "Apple"}
序列化就是如此简单,json字段名就是类的属性名,如果要修改序列化字段名,需要在类型属性上添加注解@SerializedName
:
public class Item {
@SerializedName("ID")
int id;
String name;
@NonNull
@Override
public String toString() {
return "Item{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
那么json的字符串值将变为:
{"ID": 101,"name": "Apple"}
3. Gson对象解析
将上面的json直接解析为Item
对象:
Item it = gson.fromJson(json, Item.class);
Log.d(TAG, it.toString());
打印结果如下:
Item{id=101, name='Apple'}
可见Gson的使用是非常简单的,掌握上述方法已经可以处理大部分JSON序列化问题了。
二、混淆
Gson的对象解析利用到了java反射机制,如果开启了混淆是否影响序列化和反序列化,接下来做一个简单的实验。
在build.gradle中打开混淆开关:
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
再次运行上面的代码,将得到如下类似打印:
{"ID":101,"b":"Apple"}
由于在id
字段上添加了注解序列化名称,因此ID
名称并没有因混淆被影响;而name
字段由于混淆名称变为了b
,所以JSON字段名称也变成了b
。
所以要避免混淆对系列化的影响,一是可以在需要化字段上添加注解@SerializedName
,二是防止整个类被混淆,如可在类上添加注解@Keep
,或者在混淆文件中添加
-keepclasseswithmembernames class com.ihuntto.hellogson.Item {*;}
三、序列化部分类属性
上面给出的例子是对类的属性进行全部JSON序列化,如果要进行部分序列化,该怎么办?这里有多种方式可选择。
1. 使用transient
修饰符
在不需要序列化字段前面添加transient
:
transient String nickName = "Pie";
2. 指定Modifier
字段
如不序列化private
和protected
字段:
Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.PRIVATE, Modifier.PROTECTED).create();
当然还用其他Modifier
可以选择:
package java.lang.reflect;
public class Modifier {
public static final int ABSTRACT = 1024;
public static final int FINAL = 16;
public static final int INTERFACE = 512;
public static final int NATIVE = 256;
public static final int PRIVATE = 2;
public static final int PROTECTED = 4;
public static final int PUBLIC = 1;
public static final int STATIC = 8;
public static final int STRICT = 2048;
public static final int SYNCHRONIZED = 32;
public static final int TRANSIENT = 128;
public static final int VOLATILE = 64;
...
}
3. 使用注解@Expose
在需要进行序列化的字段上添加@Expose
注解:
public class Item {
@Expose
@SerializedName("ID")
int id;
@Expose
String name;
transient String nickName = "Pie";
String otherStuff;
...
}
使用GsonBuilder
创建Gson
,以排除不包含@Expose
注解的字段:
Item item = new Item();
item.id = 101;
item.name = "Apple";
item.otherStuff = "Red";
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String json = gson.toJson(item);
Log.d(TAG, json);
序列化的json字符串打印为:
{"ID":101,"name":"Apple"}
可见nickName
和otherStuff
字段都没有被序列化。
也可以指定字段只支持序列化或反序列化:
@Expose(serialize = true, deserialize = false)
4. 自定义排除策略
如果上面还不能满足部分字段序列化需求,还可以自定义排除策略:
public class MyExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes field) {
return field.getName().startsWith("_");
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return clazz.isAnonymousClass();
}
}
排除以下划线开头的字段,以及匿名内部类不进行序列化,还可以根据FieldAttributes
和Class<?>
支持的判断添加排除策略。
综合上述4种方法,首先不推荐使用@Expose
注解,因为任何库中类的字段不支持你添加@Expose
注解的,也就是你不能序列化任何库中的类对象;其次不推荐使用Modifier
进行排除序列化字段,一是代码规范基本建议不使用public
字段,二是protected
和private
可以用来区别子类的可见性,但不是区分序列化的标志,如果实在是要使用,建议使用其他Modifier
来区分;比较建议使用transient
,因为java语言已经明确其修饰字段不进行序列化。
四、反序列化含子类的列表
如果在ArrayList<IShape>
的列表中添加了IShape
的子类,那么这个列表还能正常序列化和反序列化吗?
interface IShape {
void draw();
}
public class Circle implements IShape {
private static final String TAG = Circle.class.getSimpleName();
float x;
float y;
float radius;
public Circle() {
}
public Circle(float x, float y, float radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
Log.d(TAG, "draw " + toString());
}
@NonNull
@Override
public String toString() {
return "Circle{" +
"x=" + x +
", y=" + y +
", radius=" + radius +
'}';
}
}
public class Rectangle implements IShape {
private static final String TAG = Rectangle.class.getSimpleName();
float x;
float y;
float width;
float height;
public Rectangle() {
}
public Rectangle(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void draw() {
Log.d(TAG, "draw " + toString());
}
@NonNull
@Override
public String toString() {
return "Rectangle{" +
"x=" + x +
", y=" + y +
", width=" + width +
", height=" + height +
'}';
}
}
public class ShapeHolder {
List<IShape> shapes = new ArrayList<>();
}
IShape circle = new Circle(0, 0, 10);
IShape rectangle = new Rectangle(0, 0, 20, 10);
ShapeHolder holder = new ShapeHolder();
holder.shapes.add(circle);
holder.shapes.add(rectangle);
Gson gson = new Gson();
String json = gson.toJson(holder);
Log.d(TAG, json);
上述代码可以正常序列化:
{"shapes":[{"radius":10.0,"x":0.0,"y":0.0},{"height":10.0,"width":20.0,"x":0.0,"y":0.0}]}
但是将json字符串进行反序列化时:
ShapeHolder shapeHolder = gson.fromJson(json, ShapeHolder.class);
将得到如下异常:
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
,首先在Circle
和Rectangle
中添加type
字段以区分不同的序列化类:
public class Circle implements IShape {
...
String type = "circle";
...
}
public class Rectangle implements IShape {
...
String type = "rectangle";
...
}
public class IShapeDeserializer implements JsonDeserializer<IShape> {
private final Gson mGson = new Gson();
private final HashMap<String, Class<? extends IShape>> mTypeMap = new HashMap<>();
public IShapeDeserializer() {
mTypeMap.put("circle", Circle.class);
mTypeMap.put("rectangle", Rectangle.class);
}
@Override
public IShape deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Class<? extends IShape> clazz = mTypeMap.get(json.getAsJsonObject().get("type").getAsString());
return mGson.fromJson(json, clazz);
}
}
现在进行序列化与反序列化:
IShape circle = new Circle(0, 0, 10);
IShape rectangle = new Rectangle(0, 0, 20, 10);
ShapeHolder holder = new ShapeHolder();
holder.shapes.add(circle);
holder.shapes.add(rectangle);
Gson gson = new GsonBuilder().registerTypeAdapter(IShape.class, new IShapeDeserializer()).create();
String json = gson.toJson(holder);
Log.d(TAG, json);
ShapeHolder shapeHolder = gson.fromJson(json, ShapeHolder.class);
for (IShape shape : shapeHolder.shapes) {
shape.draw();
}
运行上述代码将得到如下打印:
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}]}
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}
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
:
public class ShapeCompounds implements IShape {
private static final String TAG = ShapeCompounds.class.getSimpleName();
String type = "shape-compounds";
List<IShape> shapes = new ArrayList<>();
@Override
public void draw() {
Log.d(TAG, "draw " + toString());
}
@NonNull
@Override
public String toString() {
return "ShapeCompounds{" +
"type='" + type + '\'' +
", shapes=" + shapes +
'}';
}
}
并且在IShapeDeserializer
进行注册:
public class IShapeDeserializer implements JsonDeserializer<IShape> {
...
public IShapeDeserializer() {
...
mTypeMap.put("shape-compounds", ShapeCompounds.class);
}
...
}
那么下面这段代码还能运行正常吗?
IShape circle = new Circle(0, 0, 10);
IShape rectangle = new Rectangle(0, 0, 20, 10);
ShapeCompounds compounds = new ShapeCompounds();
compounds.shapes.add(circle);
compounds.shapes.add(rectangle);
ShapeHolder holder = new ShapeHolder();
holder.shapes.add(circle);
holder.shapes.add(rectangle);
holder.shapes.add(compounds);
Gson gson = new GsonBuilder().registerTypeAdapter(IShape.class, new IShapeDeserializer()).create();
String json = gson.toJson(holder);
Log.d(TAG, json);
ShapeHolder shapeHolder = gson.fromJson(json, ShapeHolder.class);
for (IShape shape : shapeHolder.shapes) {
shape.draw();
}
很不辛,上述代码可以序列化成功,但反序列化失败,如果在IShapeDeserializer
的deserialize
方法出加上打印可以知道原因是无法反序列化ShapeCompounds
,因为序列化ShapeCompounds
的Gson
为普通Gson
,并没有注册TypeAdapter
,因此同样需要为IShapeDeserializer
注册TypeAdapter
:
public class IShapeDeserializer implements JsonDeserializer<IShape> {
private static Gson sInstance;
private final HashMap<String, Class<? extends IShape>> mTypeMap = new HashMap<>();
private IShapeDeserializer() {
mTypeMap.put("circle", Circle.class);
mTypeMap.put("rectangle", Rectangle.class);
mTypeMap.put("shape-compounds", ShapeCompounds.class);
}
@Override
public IShape deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Class<? extends IShape> clazz = mTypeMap.get(json.getAsJsonObject().get("type").getAsString());
return sInstance.fromJson(json, clazz);
}
public static Gson crate() {
if (sInstance == null) {
sInstance = new GsonBuilder().registerTypeAdapter(IShape.class, new IShapeDeserializer()).create();
}
return sInstance;
}
}
将测试代码中的gson
对象创建方法替换为:
Gson gson = IShapeDeserializer.crate();
运行测试代码就可以得到正常打印了:
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"}]}
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}
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}
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过程中遇到的问题及解决的方法,后续遇到新的问题再进行更新。
参考
Android中使用Gson的更多相关文章
- Android中使用Gson解析JSON数据的两种方法
Json是一种类似于XML的通用数据交换格式,具有比XML更高的传输效率;本文将介绍两种方法解析JSON数据,需要的朋友可以参考下 Json是一种类似于XML的通用数据交换格式,具有比XML更高的 ...
- Android中使用Gson解析JSON数据
Android中使用Gson解析JSON数据 在Android中可以使用Gson解析JSON数据 首先,从 code.google.com/p/google-gson/downloads/list ...
- Android中Retrifit使用总结
Android中网络请求框架Retrofit的使用注意事项 1.Retrofit是基于OkHttp网络请求框架的二次封装而已,懂Okhttp的小伙伴,那么Retrofit也就基本都会. 2.Retro ...
- Android 中的Json解析工具fastjson 、序列化、反序列化
Android中通常需要访问服务器,然而服务器返回的数据很多时候都是Json格式 1.fastjson简介 阿里巴巴FastJson是一个Json处理工具包,包括“序列化”和“反序列化”两部分,它具备 ...
- Android 中的缓存机制与实现
Android开发本质上就是手机和互联网中的web服务器之间进行通信,就必然需要从服务端获取数据,而反复通过网络获取数据是比较耗时的,特别是访问比较多的时候,会极大影响了性能,Android中可通过二 ...
- Android开源库--Gson谷歌官方json解析库
官方文档地址:http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/index.html 官方网站:http://code.go ...
- Android Volley和Gson实现网络数据加载
Android Volley和Gson实现网络数据加载 先看接口 1 升级接口 http://s.meibeike.com/mcloud/ota/cloudService POST请求 参数列表如下 ...
- Android 中解析 JSON
有什么不懂的可以去官网去看看:www.json.org 在google android中也有关于解析JSON的类库:JsonReader,但是只能在3.0以后的版本中才可以用,在这里我们用google ...
- 1.Android中解析json程序代码
Android程序解析json数据可以通过gson的方式,这种情况需要导入相应的jar包.测试代码如下: @Override protected void onCreate(Bundle savedI ...
- Android探究之Gson@SerializedName
@SerializedName注解的意义 当我们使用Gson解析Json数据时都会创建一个对应实体类,有时候Json数据里面的字段是Java关键词或者Json数据里面的字段太简单,我们想在实体类中自定 ...
随机推荐
- 聊聊数据库事务内嵌TCP连接
最近再看项目代码,发现很多的service里面,喜欢在事务内部再去调用HTTP请求,简单分析下此种方式的利弊与解决策略. 概述 在数据库内部嵌套TCP连接(一般是HTTP调用或是RPC远程调用). @ ...
- 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 ...
- 全网最详细Java-JVM
Java-JVM ①JVM概述 ❶基本介绍 JVM:全称 Java Virtual Machine,一个虚拟计算机,Java 程序的运行环境(Java二进制字节码的运行环境) 特点: Java 虚拟机 ...
- MFC中使用函数实现ini文件的连续读写
实现的思路: 首先通过读取文件中的count值,确定当前信息条数: 第二步:将count进行累加,把信息写到累加后的键值"="的后面: 第三步:写入count累加值,实现连续读写: ...
- JS深入学习笔记 - 第三章.变量作用域与内存
1.原始值和引用值 ECMScript变量包含两种不同类型是数据:原始值和引用值. 原始值:最简单的数据.有6中原始值:Undefined.Null.Boolean.Number.String和Sym ...
- 汇编debug的安装
实验一查看CPU和内存,用机器指令和汇编指令编程 在做实验前需要debug命令. 工具:dosbox,debug.exe 安装:dosbox :https://www.dosbox.com/ debu ...
- 研发三维GIS系统笔记/实现wgs84投影-002
四叉树代码修改完善 原来的代码中,没有使用投影转换,直接使用的是世界坐标(单位是米), CELLQuadTree::CELLQuadTree( CELLTerrainInterface* pInter ...
- CF451B
题目简化和分析: 这题就是判断将一段翻转后是否能变为升序的数组. 我的方法是保存原数组每一个数出现的位置(相同任意一个),让后另外用一个数组存储排好序后的原数组,逐一进行比较. 若同,则跳到下一个元素 ...
- Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法
Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,此处测试代码如下,这里使用add方法: 1 public class main { 2 public static vo ...
- 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& ...