Android開發(fā)中會經(jīng)常解析json渐溶,使用retrofit框架常用gson解析json。Gson有一定的容錯機(jī)制或者自定轉(zhuǎn)換,如"1"
可以轉(zhuǎn)換為int 1
,long 1
等等票罐,反之也一樣。如果服務(wù)端傳空字符串過來泞边,這時怎么辦(服務(wù)端數(shù)據(jù)不修改情況下该押,下同)?
結(jié)構(gòu)
服務(wù)端數(shù)據(jù)(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然后使用字段時進(jìn)行日期轉(zhuǎn)換蚕礼,這里假設(shè)解析后就是需要的Date對象烟具。如果用默認(rèn)的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<GameData> {
@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<GameData> {
@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<GameData> {
//序列化
@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
如果要為一系列的適配器進(jìn)行注冊, 自定義TypeAdapterFactory
比較有用
class GameDataTypeFactory implements TypeAdapterFactory{
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if(type.getRawType() == GameData.class){
return (TypeAdapter<T>) 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<Number> numberSerializer = new JsonSerializer<Number>() {
@Override
public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive("cn:"+String.valueOf(src));
}
};
//希望通過 registerTypeAdapter 注冊父類的序列化來實現(xiàn)子類的序列化
@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
注冊父類的序列化來實現(xiàn)子類的序列化是行不通的,需要使用 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
進(jìn)行注冊,需要配置Gson. @JsonAdapter
注解正是為了無縫注冊而生
@JsonAdapter(GameDataDeserializer.class)
public class GameData {
...
}
GameData data = new Gson().fromJson(Data.getJson(), GameData.class);
一個類默認(rèn)只能用一個@JsonAdapter
, @JsonAdapter
注解支持字段
public class SomeEntity {
@JsonAdapter(GameDataDeserializer2.class)
GameData data;
}
應(yīng)用
字段轉(zhuǎn)換
{
"xxx": "yyy",
"showItem": "0" // 0-顯示, 1 不顯示
}
如上的json, 通常定義兩個字符串字段即可, 使用時根據(jù)showItem
來決定是否顯示:
if("0".equals(showItem) {
...
}else {
...
}
每次這樣處理總覺得啰嗦, 即使抽取常量也是不治本(還得標(biāo)明和哪些常量比較), 既然是表示boolean的量,用boolean類型最好了, 在不要求服務(wù)端做修改的情況下, 可以使用TypeAdapter
在String和boolean間互相轉(zhuǎn)換:
/**
* [Boolean]與[String]轉(zhuǎn)換
* ```
* string boolean
* "0" -> false
* "1" -> true
* ```
*/
class BooleanAdapter : TypeAdapter<Boolean>() {
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, 因此轉(zhuǎn)換時不需要 beginObject
和beginArray
之類, 是單純的字段轉(zhuǎn)換.
這樣就可以將showItem定義成boolean類型了:
@JsonAdapter(BooleanAdapter::class)
@SerializedName("showItem")
var showItem: Boolean = false
Enum
還有一個比較實用的場景就是枚舉的轉(zhuǎn)換了. 服務(wù)端設(shè)定了類型, 客戶端解析轉(zhuǎn)換成枚舉對象
"type":"0" //0-無跳轉(zhuǎn) 1-動畫 2-漫畫 3-文章 4-專題 5-外鏈 6-評論 7-分類, 也是單純字段轉(zhuǎn)換
創(chuàng)建對應(yīng)枚舉類和TypeAdapter
(不用序列化也可以只用JsonDeserializer
), 這樣使用時就不需要和一堆012比較了,也省了注解替換枚舉的必要.
總結(jié)
- 自定義(反)序列化可以使用
TypeAdapter
,TypeAdapterFactory
,JsonDeserializer
或JsonSerializer
- 自定義(反)序列化支持
@JsonAdapter
注解,@JsonAdapter
支持字段 - 相信你已經(jīng)知道如何解決開頭提到的空字符串和不規(guī)則json之類的問題了