編輯:關於android開發
去公司實習了,沒多少時間更博客了,距離上一篇博客也有一個來月了。看標題,應該可以看出,這篇文章是講一個坑,以及如何填坑。
坑是什麼?有兩個坑,其一是fastjson的bug,其二是不規范的json字符串。如何填坑,不要著急,後文詳細說明。
首先,我們看一個json字符串
{
"doubleParam": 4.875,
"floatParam": 2.76,
"extra": {
"doubleParam": 12.23,
"floatParam": 12.54
},
}
這是一個比較規范的json字符串,如果我們使用fastjson解析它,基本上是不會有任何問題的。
解析之前,我們需要根據這個json字符串生成bean,當然你可以選擇Android Studio中的插件Gson Formattor自動生成bean。最終的兩個bean如下所示,為了代碼簡單,我直接將屬性設為了public,省略了getter和setter方法。
public class Extra {
public double doubleParam;
public float floatParam;
@Override
public String toString() {
return "Extra{" +
"doubleParam=" + doubleParam +
", floatParam=" + floatParam +
'}';
}
}
public class Bean {
public double doubleParam;
public float floatParam;
public Extra extra;
@Override
public String toString() {
return "Bean{" +
"doubleParam=" + doubleParam +
", floatParam=" + floatParam +
", extra=" + extra +
'}';
}
}
然後我們需要一個泛型的Json工具類,對於這個工具類,你要做的就是直接使用即可。
public class JsonUtil {
/**
* 將對象轉換成json
*
* @param src 對象
* @return 返回json字符串
* @throws Exception
*/
public static String toJson(T src) {
try {
return src instanceof String ? (String) src : JSON.toJSONString(src);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 將json通過類型轉換成對象
*
* @param json json字符串
* @param clazz 泛型類型
* @return 返回對象, 失敗返回NULL
*/
public static T fromJson(String json, Class clazz) {
try {
return clazz.equals(String.class) ? (T) json : JSON.parseObject(json, clazz);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 將json通過類型轉換成對象
*
* @param json json字符串
* @param typeReference 引用類型
* @return 返回對象
*/
public static T fromJson(String json, TypeReference> typeReference) {
try {
return (T) (typeReference.getType().equals(String.class) ? json
: JSON.parseObject(json, typeReference));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
我們將這個json字符串定義成一個常量,方便使用。
public class Constant {
public static final String JSON = "{\"doubleParam\":4.875,\"extra\":{\"doubleParam\":12.23,\"floatParam\":12.54},\"floatParam\":2.76}";
}
接下來就是開始解析了,直接使用JsonUtil工具類進行解析即可。
Bean bean = JsonUtil.fromJson(Constant.JSON, Bean.class);
System.out.println(bean);
只要能夠正常的輸出結果,基本上就是解析成功了。輸出結果如下
Bean{doubleParam=4.875, floatParam=2.76, extra=Extra{doubleParam=12.23, floatParam=12.54}}
以上過程,看上去十分完美,但是現實往往是殘酷的。第一個坑即將來臨。客戶端使用的json字符串往往是來自服務器端,而客戶端與服務器端的json規范有時候往往沒有統一,導致了一方必然需要兼容另一方。比如,服務器端返回的json字符串,無論是否是字符串類型,有時候都會加上一個引號,即使你的屬性值是int,float,double,boolean類型,服務器端給你返回的也是一個字符串。 就像下面這樣
{
"doubleParam": "4.875",
"extra": {
"doubleParam": "12.23",
"floatParam": "12.54"
},
"floatParam": "2.76"
}
上面這個json字符串其實是不規范的,因為不論什麼數據類型,它都加上了引號,好在使用fastjson解析這樣的數據時,也是可以正常解析的。而且解析結果十分完美,沒有一絲的錯誤。那麼為什麼說是個坑呢。
服務器返回上面這種都是字符串類型的其實都好說,不會對解析結果造成任何影響,但是往往現實也不是這樣的,有時候某一個值沒有,服務器理論上應該返回一個默認值,比如int類型應該返回”0”,然而,實際上你接受到的json字符串卻是這樣的。
{
"doubleParam": "",
"extra": {
"doubleParam": "12.23",
"floatParam": ""
},
"floatParam": "2.76"
}
可以看到,最外層的doubleParam值為空字符串,內部的extra對象中的floatParam值也為空字符串。其實即使是空,它們也應該被賦值一個默認值0,這樣,在客戶端解析時不會發生任何錯誤。問題就出在這個空字符串。一旦使用了空字符串,在客戶端解析時就會引發致命的錯誤。而這個錯誤的原因來自fastjson的一個Bug,然而我並不知道作者為什麼沒有修復這個bug。現在我們來解析一下,看看會發生什麼錯誤。代碼還是原來的配方。但是結果卻發生了翻天覆地的變化。程序報了一個錯誤,並且最終我們解析得到的bean是一個null。
com.alibaba.fastjson.JSONException: set property error, doubleParam
at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:97)
at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:43)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:400)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:310)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:115)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:548)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:215)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:191)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:150)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:255)
at cn.edu.zafu.demo.fastjson.JsonUtil.fromJson(JsonUtil.java:66)
at cn.edu.zafu.demo.Main.main(Main.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.IllegalArgumentException: Can not set double field cn.edu.zafu.demo.bean.Bean.doubleParam to null value
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeDoubleFieldAccessorImpl.set(UnsafeDoubleFieldAccessorImpl.java:80)
at java.lang.reflect.Field.set(Field.java:764)
at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:95)
... 16 more
null
發生錯誤的原因這裡不追究,如果你想知道原因,跟進源碼查看一下你就會知道為什麼會產生這個異常。當前,我使用的fastjson的版本是
compile 'com.alibaba:fastjson:1.1.46.android'
應該是android中的最新版,而且我也查過了官方的wiki中的release日志,在1.1.45版本裡面明確說明了修復過這個bug,但是實際上還是存在。
修復數值類型byte、short、int、long、float、double、boolean的值為null時,反序列化失敗的問題。
確實,其他數據類型,比如int,long,boolean是不在在這個問題的,唯獨float和double存在這個問題。於是我們需要對症下藥。
我們來翻翻fastjson的源碼。在parser包下面有一個ParserConfig,裡面有這麼一段代碼。
public FieldDeserializer createFieldDeserializer(ParserConfig mapping, Class> clazz, FieldInfo fieldInfo) {
Class> fieldClass = fieldInfo.getFieldClass();
if (fieldClass == boolean.class || fieldClass == Boolean.class) {
return new BooleanFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == int.class || fieldClass == Integer.class) {
return new IntegerFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == long.class || fieldClass == Long.class) {
return new LongFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == String.class) {
return new StringFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == List.class || fieldClass == ArrayList.class) {
return new ArrayListTypeFieldDeserializer(mapping, clazz, fieldInfo);
}
return new DefaultFieldDeserializer(mapping, clazz, fieldInfo);
}
可以看到,boolean,int,long都設置了反序列化用的類,而float和double卻沒有,於是,我們就需要對這個方法進行改造。我們新建一個PatchParserConfig類繼承ParserConfig類。我們預料的結果應該是這樣的。
public class PatchParserConfig extends ParserConfig {
@Override
public FieldDeserializer createFieldDeserializer(ParserConfig mapping, Class> clazz, FieldInfo fieldInfo) {
Class> fieldClass = fieldInfo.getFieldClass();
if (fieldClass == boolean.class || fieldClass == Boolean.class) {
return new BooleanFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == int.class || fieldClass == Integer.class) {
return new IntegerFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == long.class || fieldClass == Long.class) {
return new LongFieldDeserializer(mapping, clazz, fieldInfo);
}
// patch
if (fieldClass == float.class || fieldClass == Float.class) {
return new FloatFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == double.class || fieldClass == Double.class) {
return new DoubleFieldDeserializer(mapping, clazz, fieldInfo);
}
// patch
if (fieldClass == String.class) {
return new StringFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == List.class || fieldClass == ArrayList.class) {
return new ArrayListTypeFieldDeserializer(mapping, clazz, fieldInfo);
}
return new DefaultFieldDeserializer(mapping, clazz, fieldInfo);
}
}
很明顯的我們增加了下面的幾行代碼
// patch
if (fieldClass == float.class || fieldClass == Float.class) {
return new FloatFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == double.class || fieldClass == Double.class) {
return new DoubleFieldDeserializer(mapping, clazz, fieldInfo);
}
// patch
而FloatFieldDeserializer類和DoubleFieldDeserializer都是哪來的呢。這個很簡單,照樣畫葫蘆就可以了,在parser包裡的deserializer包種有各種反序列化用的類。我們選擇其中一個照樣畫葫蘆就ok了。比如我們選擇LongFieldDeserializer,其代碼如下
public class LongFieldDeserializer extends FieldDeserializer {
private final ObjectDeserializer fieldValueDeserilizer;
public LongFieldDeserializer(ParserConfig mapping, Class> clazz, FieldInfo fieldInfo){
super(clazz, fieldInfo);
fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
}
@Override
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map fieldValues) {
Long value;
final JSONLexer lexer = parser.getLexer();
if (lexer.token() == JSONToken.LITERAL_INT) {
long val = lexer.longValue();
lexer.nextToken(JSONToken.COMMA);
if (object == null) {
fieldValues.put(fieldInfo.getName(), val);
} else {
setValue(object, val);
}
return;
} else if (lexer.token() == JSONToken.NULL) {
value = null;
lexer.nextToken(JSONToken.COMMA);
} else {
Object obj = parser.parse();
value = TypeUtils.castToLong(obj);
}
if (value == null && getFieldClass() == long.class) {
// skip
return;
}
if (object == null) {
fieldValues.put(fieldInfo.getName(), value);
} else {
setValue(object, value);
}
}
public int getFastMatchToken() {
return fieldValueDeserilizer.getFastMatchToken();
}
}
按照這個模子,很容易的就出來了我們的兩個類。
public class DoubleFieldDeserializer extends FieldDeserializer {
private final ObjectDeserializer fieldValueDeserilizer;
public DoubleFieldDeserializer(ParserConfig mapping, Class> clazz, FieldInfo fieldInfo) {
super(clazz, fieldInfo);
fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
}
@Override
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map fieldValues) {
Double value;
final JSONLexer lexer = parser.getLexer();
if (lexer.token() == JSONToken.LITERAL_INT) {
long val = lexer.longValue();
lexer.nextToken(JSONToken.COMMA);
if (object == null) {
fieldValues.put(fieldInfo.getName(), val);
} else {
setValue(object, val);
}
return;
} else if (lexer.token() == JSONToken.NULL) {
value = null;
lexer.nextToken(JSONToken.COMMA);
} else {
Object obj = parser.parse();
value = TypeUtils.castToDouble(obj);
}
if (value == null && getFieldClass() == double.class) {
// skip
return;
}
if (object == null) {
fieldValues.put(fieldInfo.getName(), value);
} else {
setValue(object, value);
}
}
@Override
public int getFastMatchToken() {
return fieldValueDeserilizer.getFastMatchToken();
}
}
public class FloatFieldDeserializer extends FieldDeserializer {
private final ObjectDeserializer fieldValueDeserilizer;
public FloatFieldDeserializer(ParserConfig mapping, Class> clazz, FieldInfo fieldInfo) {
super(clazz, fieldInfo);
fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
}
@Override
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map fieldValues) {
Float value;
final JSONLexer lexer = parser.getLexer();
if (lexer.token() == JSONToken.LITERAL_INT) {
long val = lexer.longValue();
lexer.nextToken(JSONToken.COMMA);
if (object == null) {
fieldValues.put(fieldInfo.getName(), val);
} else {
setValue(object, val);
}
return;
} else if (lexer.token() == JSONToken.NULL) {
value = null;
lexer.nextToken(JSONToken.COMMA);
} else {
Object obj = parser.parse();
value = TypeUtils.castToFloat(obj);
}
if (value == null && getFieldClass() == float.class) {
// skip
return;
}
if (object == null) {
fieldValues.put(fieldInfo.getName(), value);
} else {
setValue(object, value);
}
}
@Override
public int getFastMatchToken() {
return fieldValueDeserilizer.getFastMatchToken();
}
萬事具備,只欠東風,那麼我們如何將我們的PatchParserConfig注入到解析的過程中去呢。我們使用android studio的快捷鍵command+左鍵一直跟進源碼,發現JsonUtil工具類中的兩個fromJson方法,最終會調用下面的兩個方法
public static final T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
int featureValues, Feature... features) {
if (input == null) {
return null;
}
for (Feature featrue : features) {
featureValues = Feature.config(featureValues, featrue, true);
}
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
if (processor instanceof ExtraTypeProvider) {
parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
}
if (processor instanceof ExtraProcessor) {
parser.getExtraProcessors().add((ExtraProcessor) processor);
}
T value = (T) parser.parseObject(clazz);
parser.handleResovleTask(value);
parser.close();
return (T) value;
}
而該函數中新建的DefaultJSONParser對象內部維持了ParserConfig的一個引用,我們可以通過setter方法修改該引用為我們自己的的PatchParserConfig,於是一切都變得如此簡單。
private static final ParserConfig PARSER_CONFIG = new PatchParserConfig();
private static final T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
int featureValues, Feature... features) {
if (input == null) {
return null;
}
for (Feature featrue : features) {
featureValues = Feature.config(featureValues, featrue, true);
}
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
parser.setConfig(PARSER_CONFIG);
if (processor instanceof ExtraTypeProvider) {
parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
}
if (processor instanceof ExtraProcessor) {
parser.getExtraProcessors().add((ExtraProcessor) processor);
}
T value = (T) parser.parseObject(clazz);
parser.handleResovleTask(value);
parser.close();
return (T) value;
}
為了更加方便使用,我們在這個方法基礎上再分裝兩個類似JSON類中的參數少的方法。
private static final T parseObject(String text, Class clazz) {
return (T) parseObject(text, (Type) clazz, PatchParserConfig.getGlobalInstance(),null,JSON.DEFAULT_PARSER_FEATURE, new Feature[0]);
}
private static final T parseObject(String text, TypeReference type, Feature... features) {
return (T) parseObject(text, type.getType(), PatchParserConfig.getGlobalInstance(), null, JSON.DEFAULT_PARSER_FEATURE, features);
}
接著改造我們原來JsonUtil類中的fromJson的兩個方法,將其調用執行自己內部的靜態函數parserObject,如下。
public static T fromJson(String json, Class clazz) {
try {
return clazz.equals(String.class) ? (T) json : parseObject(json, clazz);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static T fromJson(String json, TypeReference> typeReference) {
try {
return (T) (typeReference.getType().equals(String.class) ? json
: parseObject(json, typeReference));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
最終,暴露給客戶端的就是這兩個方法。我們一個狸貓換太子,一下子就對服務器返回的空字符串進行了兼容。下面測試一下能否正常反序列化。輸出結果如下
Bean{doubleParam=0.0, floatParam=2.76, extra=Extra{doubleParam=12.23, floatParam=0.0}}
異常消失了,世界都安靜了。
android studio導入PullToRefresh教程 1.新建一個Android項目,下載好Android-PullToRefresh-master,並解壓
我的android學習經歷8,android學習經歷8android簽名打包apk文件 1.在要打包的項目上右擊打開Android Tools,然後打開Export An
安卓 自定義AlertDialog對話框(加載提示框),安卓alertdialogAlertDialog有以下六種使用方法: 一、簡單的AlertDialog(只顯示一段
在android上要實現類似Launch的抽屜效果,大家一定首先會想起SlidingDrawer。