Android开发中会经常解析json,使用retrofit框架常用gson解析json。Gson有一定的容错机制或者自定转换,如"1"
可以转换为int 1
,long 1
等等,反之也一样。如果服务端传空字符串过来,这时怎么办(服务端数据不修改情况下,下同)?
结构
服务端数据(create_time
为时间秒值, 后文Data.getJson()
返回的是下面的json字符串)
{
"id": "14",
"name": "test1",
"create_time": "1548122663"
}
解析对象
public class GameData {
public String id;
public String name;
public Date createTime;
@Override
public String toString() {
return "{id='" + id + "\', name='" + name + "\', createTime=" + createTime + '}';
}
}
通常解析时不会填Date这种类型,因此可以改成String然后使用字段时进行日期转换,这里假设解析后就是需要的Date对象。如果用默认的Gson解析将得到一个Json解析异常
// com.google.gson.JsonSyntaxException: 1548122663
GameData data = new Gson().fromJson(Data.getJson(), GameData.class);
JsonSerializer与JsonDeserializer
JsonDeserializer
表示自定义json序列化, JsonDeserializer
表示自定义json反序列化,来看下它们怎么用。
自定义json反序列化
// 自定义 GameDat 反序列化
public class GameDataDeserializer implements JsonDeserializer {
@Override
public GameData deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
GameData data = new GameData();
if (jsonElement instanceof JsonObject) {
JsonObject json = (JsonObject) jsonElement;
data.id = json.get("id").getAsString();
data.name = json.get("name").getAsString();
data.createTime = new Date(json.get("create_time").getAsLong() * 1000);
}
return data;
}
}
// 自定义 GameDat 反序列化使用
@Test
public void testCustomDeserializer() throws Exception {
Gson gson = new GsonBuilder()
// 注册反序列化适配器
.registerTypeAdapter(GameData.class, new GameDataDeserializer())
.create();
GameData data = gson.fromJson(Data.getJson(), GameData.class);
System.out.println(data);//{id='14', name='test1', createTime=Tue Jan 22 10:04:23 CST 2019}
}
自定义序列化
//Gson自带序列化的效果
@Test
public void testDefaultSerializer() throws Exception{
GameData data = new GameData();
data.id = "14";
data.name = "testSerializer";
data.createTime = new Date(1548122663L*1000);
System.out.println(new Gson().toJson(data));
//{"id":"14","name":"testSerializer","create_time":"Jan 22, 2019 10:04:23 AM"}
}
// 自定义 GameDate 序列化
public class GameDataSerializer implements JsonSerializer {
@Override
public JsonElement serialize(GameData src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject json = new JsonObject();
json.addProperty("id", src.id);
json.addProperty("name", src.name);
json.addProperty("create_time", String.valueOf(src.createTime.getTime() / 1000));
return json;
}
}
//使用自定义 GameDate 序列化
@Test
public void testCustomSerializer() throws Exception {
Gson gson = new GsonBuilder()
.registerTypeAdapter(GameData.class, new GameDataSerializer())
.create();
GameData data = new GameData();
data.id = "14";
data.name = "testSerializer";
data.createTime = new Date(1548122663L * 1000);
System.out.println(gson.toJson(data));
//{"id":"14","name":"testSerializer","create_time":"1548122663"}
}
TypeAdapter
如果序列化和反序列化都需要,可以考虑使用 TypeAdapter
。
// 自定义 GameDataTypeAdapter
public class GameDataTypeAdapter extends TypeAdapter {
//序列化
@Override
public void write(JsonWriter out, GameData data) throws IOException {
out.beginObject();
out.name("id").value(data.id)
.name("name").value(data.name)
.name("create_time").value(String.valueOf(data.createTime.getTime() / 1000));
out.endObject();
}
// 反序列化
@Override
public GameData read(JsonReader in) throws IOException {
in.beginObject();
GameData data = new GameData();
while (in.peek() != JsonToken.END_OBJECT) {
String name = in.nextName();
if ("id".equals(name)) {
data.id = in.nextString();
} else if ("name".equals(name)) {
data.name = in.nextString();
} else if ("create_time".equals(name)) {
data.createTime = new Date(in.nextLong() * 1000);
}
}
in.endObject();
return data;
}
}
@Test
public void testTypeAdapter() throws Exception {
Gson gson = new GsonBuilder()
.registerTypeAdapter(GameData.class, new GameDataTypeAdapter())
.create();
GameData data = gson.fromJson(Data.getJson(), GameData.class);
System.out.println(data);
System.out.println(gson.toJson(data));
//{id='14', name='test1', createTime=Tue Jan 22 10:04:23 CST 2019}
//{"id":"14","name":"test1","create_time":"1548122663"}
}
TypeAdapterFactory
如果要为一系列的适配器进行注册, 自定义TypeAdapterFactory
比较有用
class GameDataTypeFactory implements TypeAdapterFactory{
@Override
public TypeAdapter create(Gson gson, TypeToken type) {
if(type.getRawType() == GameData.class){
return (TypeAdapter) new GameDataTypeAdapter();
}
return null;
}
}
@Test
public void testTypeAdapterFactory() throws Exception {
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new GameDataTypeFactory())
.create();
GameData data = gson.fromJson(Data.getJson(), GameData.class);
System.out.println(data);
System.out.println(gson.toJson(data));
//{id='14', name='test1', createTime=Tue Jan 22 10:04:23 CST 2019}
//{"id":"14","name":"test1","create_time":"1548122663"}
}
registerAdapter
JsonSerializer numberSerializer = new JsonSerializer() {
@Override
public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive("cn:"+String.valueOf(src));
}
};
//希望通过 registerTypeAdapter 注册父类的序列化来实现子类的序列化
@Test
public void testRegisterTypeAdapterByBaseClass() throws Exception {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Number.class, numberSerializer)
.create();
System.out.println(gson.toJson(100));//100
}
// 只好通过子类一个一个的注册了
@Test
public void testRegisterTypeAdapterEachSubclass() throws Exception {
Gson gson = new GsonBuilder()
.registerTypeAdapter(Integer.class, numberSerializer)
.registerTypeAdapter(Long.class, numberSerializer)
.registerTypeAdapter(Float.class, numberSerializer)
.registerTypeAdapter(Double.class, numberSerializer)
.create();
System.out.println(gson.toJson(100));//cn:100
}
上面说明通过registerTypeAdapter
注册父类的序列化来实现子类的序列化是行不通的,需要使用 registerTypeHierarchyAdapter
@Test
public void testRegisterTypeHierarchyAdapter() throws Exception {
Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(Number.class, numberSerializer)
.create();
System.out.println(gson.toJson(100));//cn:100
}
@JsonAdapter注解
上面的TypeAdapter
,JsonDeserializer
和JsonSerializer
的使用都需要通过GsonBuilder
的registerTypeAdapter
或registerTypeHierarchyAdapter
进行注册,需要配置Gson. @JsonAdapter
注解正是为了无缝注册而生
@JsonAdapter(GameDataDeserializer.class)
public class GameData {
...
}
GameData data = new Gson().fromJson(Data.getJson(), GameData.class);
一个类默认只能用一个@JsonAdapter
, @JsonAdapter
注解支持字段
public class SomeEntity {
@JsonAdapter(GameDataDeserializer2.class)
GameData data;
}
应用
字段转换
{
"xxx": "yyy",
"showItem": "0" // 0-显示, 1 不显示
}
如上的json, 通常定义两个字符串字段即可, 使用时根据showItem
来决定是否显示:
if("0".equals(showItem) {
...
}else {
...
}
每次这样处理总觉得啰嗦, 即使抽取常量也是不治本(还得标明和哪些常量比较), 既然是表示boolean的量,用boolean类型最好了, 在不要求服务端做修改的情况下, 可以使用TypeAdapter
在String和boolean间互相转换:
/**
* [Boolean]与[String]转换
* ```
* string boolean
* "0" -> false
* "1" -> true
* ```
*/
class BooleanAdapter : TypeAdapter() {
override fun write(out: JsonWriter, value: Boolean?) {
out.value(if (value == true) 1 else 0)
}
override fun read(`in`: JsonReader): Boolean {
if (`in`.peek() == JsonToken.NULL) {
`in`.nextNull()
return false
}
return try {
val nextString = `in`.nextString()
!nextString.isNullOrBlank() && "0" != nextString
} catch (e: Exception) {
false
}
}
}
注: 项目中使用的kotlin版本直接粘贴了, java类似. 另外这里showItem
并不是一个完整的json, 因此转换时不需要 beginObject
和beginArray
之类, 是单纯的字段转换.
这样就可以将showItem定义成boolean类型了:
@JsonAdapter(BooleanAdapter::class)
@SerializedName("showItem")
var showItem: Boolean = false
Enum
还有一个比较实用的场景就是枚举的转换了. 服务端设定了类型, 客户端解析转换成枚举对象
"type":"0" //0-无跳转 1-动画 2-漫画 3-文章 4-专题 5-外链 6-评论 7-分类, 也是单纯字段转换
创建对应枚举类和TypeAdapter
(不用序列化也可以只用JsonDeserializer
), 这样使用时就不需要和一堆012比较了,也省了注解替换枚举的必要.
总结
- 自定义(反)序列化可以使用
TypeAdapter
,TypeAdapterFactory
,JsonDeserializer
或JsonSerializer
- 自定义(反)序列化支持
@JsonAdapter
注解,@JsonAdapter
支持字段 - 相信你已经知道如何解决开头提到的空字符串和不规则json之类的问题了