From 29db125458e1c68002ecec1f64a3860da699edec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BD=AE=E5=AD=90=E5=93=A5?= Date: Sat, 4 Nov 2023 19:49:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20List=20=E5=92=8C=20Map=20?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E8=A7=A3=E6=9E=90=E5=AE=B9=E9=94=99=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=B4=A9=E6=BA=83=E7=9A=84=E9=97=AE=E9=A2=98=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20List=20=E5=92=8C=20Map=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E8=BF=94=E5=9B=9E=E7=A9=BA=E5=92=8C=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E8=B5=8B=E5=80=BC=E5=A4=84=E7=90=86=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20Gson=20=E8=A7=A3=E6=9E=90=E5=AE=B9?= =?UTF-8?q?=E9=94=99=E5=9B=9E=E8=B0=83=E7=B1=BB=E5=90=8D=E7=A7=B0=E5=8F=8A?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=96=B9=E6=B3=95=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 51 +++++++++++-------- app/build.gradle | 9 ++-- app/src/androidTest/assets/AbnormalJson.json | 15 ++++-- app/src/androidTest/assets/NormalJson.json | 5 +- .../hjq/gson/factory/test/DataClassBean.kt | 7 ++- .../com/hjq/gson/factory/test/JsonBean.java | 10 ++-- .../hjq/gson/factory/test/JsonUnitTest.java | 42 +++++++++++---- build.gradle | 21 ++++++-- library/build.gradle | 17 ++++--- .../com/hjq/gson/factory/GsonFactory.java | 12 ++--- .../com/hjq/gson/factory/JsonCallback.java | 22 -------- .../gson/factory/ParseExceptionCallback.java | 41 +++++++++++++++ .../factory/data/BigDecimalTypeAdapter.java | 7 +-- .../gson/factory/data/BooleanTypeAdapter.java | 16 ++++-- .../gson/factory/data/DoubleTypeAdapter.java | 7 +-- .../gson/factory/data/FloatTypeAdapter.java | 7 +-- .../gson/factory/data/IntegerTypeAdapter.java | 7 +-- .../factory/data/JSONArrayTypeAdapter.java | 10 ++-- .../factory/data/JSONObjectTypeAdapter.java | 10 ++-- .../gson/factory/data/LongTypeAdapter.java | 7 +-- .../gson/factory/data/StringTypeAdapter.java | 7 +-- .../element/CollectionTypeAdapter.java | 29 +++++++---- .../gson/factory/element/MapTypeAdapter.java | 39 ++++++++------ .../element/ReflectiveTypeAdapter.java | 12 ++--- 24 files changed, 262 insertions(+), 148 deletions(-) delete mode 100644 library/src/main/java/com/hjq/gson/factory/JsonCallback.java create mode 100644 library/src/main/java/com/hjq/gson/factory/ParseExceptionCallback.java diff --git a/README.md b/README.md index 802ed6d..1a33554 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ allprojects { ```groovy dependencyResolutionManagement { repositories { - // JitPack 远程仓库:https://jitpack.io + // JitPack 远程仓库:https://jitpack.io[NameThatColor-1.7.4-fix.jar](..%2FStudioPlugins%2Fplugin%2FNameThatColor-1.7.4-fix.jar) maven { url 'https://jitpack.io' } } } @@ -39,7 +39,7 @@ android { dependencies { // Gson 解析容错:https://github.com/getActivity/GsonFactory - implementation 'com.github.getActivity:GsonFactory:8.0' + implementation 'com.github.getActivity:GsonFactory:9.0' // Json 解析框架:https://github.com/google/gson implementation 'com.google.code.gson:gson:2.10.1' } @@ -76,16 +76,8 @@ GsonFactory.registerInstanceCreator(Type type, InstanceCreator creator); // 添加反射访问过滤器 GsonFactory.addReflectionAccessFilter(ReflectionAccessFilter filter); -// 设置 Json 解析容错监听 -GsonFactory.setJsonCallback(new JsonCallback() { - - @Override - public void onTypeException(TypeToken typeToken, String fieldName, JsonToken jsonToken) { - // Log.e("GsonFactory", "类型解析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken); - // 上报到 Bugly 错误列表中 - CrashReport.postCatchedException(new IllegalArgumentException("类型解析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken)); - } -}); +// 设置 Json 解析容错回调对象 +GsonFactory.setParseExceptionCallback(ParseExceptionCallback callback); ``` #### 框架混淆规则 @@ -165,6 +157,8 @@ class XxxBean { * 那么这到底是为什么呢?聊到这个就不得不先说一下 Gson 解析的机制,我们都知道 Gson 在解析一个 Bean 类的时候,会反射创建一个对象出来,但是大家不知道的是,Gson 会根据 Bean 类的字段名去解析 Json 串中对应的值,然后简单粗暴进行反射赋值,你没有听错,简单粗暴,如果后台返回这个 `age` 字段的值为空,那么 `age` 就会被赋值为空,但是你又在 Kotlin 中声明了 `age` 变量不为空,外层一调用,触发 `NullPointerException` 也是在预料之中。 +* 另外针对 List 和 Map 类型的对象,后台如果有返回 null 或者错误类型数据的时候,框架也会返回一个不为空但是集合大小为 0 的 List 对象或者 Map 对象,避免在 Kotlin 字段上面自定义字段不为空,但是后台返回空的情况导致出现的空指针异常。 + * 框架目前的处理方案是,如果后台没有返回这个字段的值,又或者返回这个值为空,则不会赋值给类的字段,因为 Gson 那样做是不合理的,会导致我在 Kotlin 上面使用 Gson 是有问题,变量不定义成可空,每次用基本数据类型还得去做判空,定义成非空,一用还会触发 `NullPointerException`,前后夹击,腹背受敌。 #### 适配 Kotlin 默认值介绍 @@ -411,7 +405,7 @@ public final class DataClassBean { } ``` -* 框架的解决方案是:反射最后第一个参数类型为 DefaultConstructorMarker,然后传入空对象即可,最后第二个参数类型为 int 的构造函数,并且让最后第二个参数的位运算逻辑为 true,让它走到默认值赋值那里,这样可以选择传入 `Integer.MAX_VALUE`,这样每次使用它去 & 不大于 0 的某个值,都会等于某个值,也就是不会等于 0,这样就能保证它的运算条件一直为 true,也就是使用默认值,其他参数传值的话,如果是基本数据类型,就传入基本数据类型的默认值,如果是对象类型,则直接传入 null。这样就完成了对 Kotlin Data Class 类默认值不生效问题的处理。 +* 框架的解决方案是:反射最后第一个参数类型为 DefaultConstructorMarker,然后传入空对象即可,最后第二个参数类型为 int 的构造函数,并且让最后第二个参数的位运算逻辑为 true,让它走到默认值赋值那里,这样可以选择传入 `Integer.MAX_VALUE`,这样每次使用它去 & 不大于 0 的某个值,都会等于某个值,也就是不会等于 0,这样就能保证它的运算条件一直为 true,也就是使用默认值,其他参数传值的话,如果是基本数据类型,就传入基本数据类型的默认值,如果是对象类型,则直接传入 null。这样就解决了 Gson 反射 Kotlin Data Class 类出现字段默认值不生效的问题。 ## 常见疑问解答 @@ -450,7 +444,7 @@ new GsonBuilder() * 如果你们的后台用的是 PHP,那我十分推荐你使用这个框架,因为 PHP 返回的数据结构很乱,这块经历过的人都懂,说多了都是泪,没经历过的人怎么说都不懂。 -* 如果你们的后台用的是 Java,那么可以根据实际情况而定,可用可不用,但是最好用,作为一种兜底方案,这样就能防止后台突然某一天不讲码德,例如我现在的公司的后台全是用 Java 开发的,但是 Bugly 还是有上报关于 Gson 解析的异常,下面是通过 `GsonFactory.setJsonCallback` 采集到的数据,大家可以参考参考: +* 如果你们的后台用的是 Java,那么可以根据实际情况而定,可用可不用,但是最好用,作为一种兜底方案,这样就能防止后台突然某一天不讲码德,例如我现在的公司的后台全是用 Java 开发的,但是 Bugly 还是有上报关于 Gson 解析的异常,下面是通过 `GsonFactory.setParseExceptionCallback` 采集到的数据,大家可以参考参考: ![](picture/bugly_report_error.jpg) @@ -478,20 +472,33 @@ new GsonBuilder() #### 使用了这个框架后,我如何知道出现了 Json 错误,从而保证问题不被掩盖? -* 对于这个问题,解决方案也很简单,使用 `GsonFactory.setJsonCallback` API,如果后台返回了错误的数据结构,在调试模式下,直接抛出异常即可,开发者可以第一时间得知;而到了线上模式,对这个问题进行上报即可,保证不漏掉任何一个问题(可上传到后台或者 Bugly 错误列表中),示例代码如下: +* 对于这个问题,解决方案也很简单,使用 `GsonFactory.setParseExceptionCallback` API,如果后台返回了错误的数据结构,在调试模式下,直接抛出异常即可,开发者可以第一时间得知;而到了线上模式,对这个问题进行上报即可,保证不漏掉任何一个问题(可上传到后台或者 Bugly 错误列表中),示例代码如下: ```java // 设置 Json 解析容错监听 -GsonFactory.setJsonCallback(new JsonCallback() { +GsonFactory.setParseExceptionCallback(new ParseExceptionCallback() { @Override - public void onTypeException(TypeToken typeToken, String fieldName, JsonToken jsonToken) { + public void onParseObjectException(TypeToken typeToken, String fieldName, JsonToken jsonToken) { + handlerGsonParseException("解析对象析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken); + } + + @Override + public void onParseListException(TypeToken typeToken, String fieldName, JsonToken listItemJsonToken) { + handlerGsonParseException("解析 List 异常:" + typeToken + "#" + fieldName + ",后台返回的条目类型为:" + listItemJsonToken); + } + + @Override + public void onParseMapException(TypeToken typeToken, String fieldName, String mapItemKey, JsonToken mapItemJsonToken) { + handlerGsonParseException("解析 Map 异常:" + typeToken + "#" + fieldName + ",mapItemKey = " + mapItemKey + ",后台返回的条目类型为:" + mapItemJsonToken); + } + + private void handlerGsonParseException(String message) { + Log.e(TAG, message); if (BuildConfig.DEBUG) { - // 直接抛出异常 - throw new IllegalArgumentException("类型解析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken); - } else { - // 上报到 Bugly 错误列表 - CrashReport.postCatchedException(new IllegalArgumentException("类型解析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken)); + throw new IllegalArgumentException(message); + } else { + CrashReport.postCatchedException(new IllegalArgumentException(message)); } } }); diff --git a/app/build.gradle b/app/build.gradle index f7e389c..cedb5db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "com.hjq.gson.factory.demo" minSdkVersion 16 targetSdkVersion 31 - versionCode 80 - versionName "8.0" + versionCode 900 + versionName "9.0" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } @@ -43,9 +43,9 @@ android { } } - applicationVariants.all { variant -> + applicationVariants.configureEach { variant -> // apk 输出文件名配置 - variant.outputs.all { output -> + variant.outputs.configureEach { output -> outputFileName = rootProject.getName() + '.apk' } } @@ -64,6 +64,7 @@ dependencies { // Json 解析框架:https://github.com/google/gson // noinspection GradleDependency androidTestImplementation 'com.google.code.gson:gson:2.10.1' + implementation 'com.google.code.gson:gson:2.10.1' // AndroidX 库:https://github.com/androidx/androidx implementation 'androidx.appcompat:appcompat:1.4.0' diff --git a/app/src/androidTest/assets/AbnormalJson.json b/app/src/androidTest/assets/AbnormalJson.json index b3a2467..65f3995 100644 --- a/app/src/androidTest/assets/AbnormalJson.json +++ b/app/src/androidTest/assets/AbnormalJson.json @@ -5,7 +5,7 @@ "bigDecimal2" : [], "bigDecimal3" : {}, "booleanTest1" : 0, - "booleanTest2" : 1, + "booleanTest2" : "hello", "booleanTest3" : null, "booleanTest4" : "true", "booleanTest5" : [], @@ -27,10 +27,11 @@ "intTest5" : {}, "jsonArray" : {}, "jsonObject" : [], - "listTest1" : true, + "listTest1" : [], "listTest2" : {}, "listTest3" : "", - "listTest4" : null, + "listTest4" : ["true", "false"], + "listTest5" : ["你好", "1.2345", "are you ok", "5.4321"], "longTest1" : 1.1, "longTest2" : null, "longTest3" : "2.2", @@ -38,6 +39,14 @@ "longTest5" : {}, "map1" : "", "map2" : false, + "map3" : [], + "map4" : { + "a": 1234, + "b": "1234", + "c": 1234.21, + "d": "哈哈", + "e": false + }, "stringTest1" : null, "stringTest2" : false, "stringTest3" : 123, diff --git a/app/src/androidTest/assets/NormalJson.json b/app/src/androidTest/assets/NormalJson.json index ec9dfa6..ba8e0f1 100644 --- a/app/src/androidTest/assets/NormalJson.json +++ b/app/src/androidTest/assets/NormalJson.json @@ -29,6 +29,7 @@ "listTest2" : ["a", "b", "c"], "listTest3" : [1, 2, 3], "listTest4" : [true, false], + "listTest5" : ["1.0", "1.1", "1.2"], "longTest1" : 1, "longTest2" : -2, "longTest3" : 9223372036854775807, @@ -37,8 +38,10 @@ "1" : false }, "map2" : { - "number" : 123456789 + "a" : 123456789 }, + "map3" : {}, + "map4" : null, "stringTest1" : null, "stringTest2" : "", "stringTest3" : "字符串" diff --git a/app/src/androidTest/java/com/hjq/gson/factory/test/DataClassBean.kt b/app/src/androidTest/java/com/hjq/gson/factory/test/DataClassBean.kt index 22af6ce..0872ace 100644 --- a/app/src/androidTest/java/com/hjq/gson/factory/test/DataClassBean.kt +++ b/app/src/androidTest/java/com/hjq/gson/factory/test/DataClassBean.kt @@ -1,3 +1,8 @@ package com.hjq.gson.factory.test -data class DataClassBean(val name: String?, val age: Int = 18, val address: String?, val birthday: Long = System.currentTimeMillis()) \ No newline at end of file +data class DataClassBean( + val name: String?, + val age: Int = 18, + val address: String?, + val birthday: Long = System.currentTimeMillis() +) \ No newline at end of file diff --git a/app/src/androidTest/java/com/hjq/gson/factory/test/JsonBean.java b/app/src/androidTest/java/com/hjq/gson/factory/test/JsonBean.java index 31b0686..ddad2d8 100644 --- a/app/src/androidTest/java/com/hjq/gson/factory/test/JsonBean.java +++ b/app/src/androidTest/java/com/hjq/gson/factory/test/JsonBean.java @@ -1,11 +1,10 @@ package com.hjq.gson.factory.test; -import org.json.JSONArray; -import org.json.JSONObject; - import java.math.BigDecimal; import java.util.List; import java.util.Map; +import org.json.JSONArray; +import org.json.JSONObject; /** * author : Android 轮子哥 @@ -16,9 +15,10 @@ public final class JsonBean { private List listTest1; - private List listTest2; + private List listTest2; private List listTest3; private List listTest4; + private List listTest5; private boolean booleanTest1; private boolean booleanTest2; @@ -66,6 +66,8 @@ public final class JsonBean { private Map map1; private Map map2; + private Map map3; + private Map map4; private JSONObject jsonObject; private JSONArray jsonArray; diff --git a/app/src/androidTest/java/com/hjq/gson/factory/test/JsonUnitTest.java b/app/src/androidTest/java/com/hjq/gson/factory/test/JsonUnitTest.java index 037d980..c004d20 100644 --- a/app/src/androidTest/java/com/hjq/gson/factory/test/JsonUnitTest.java +++ b/app/src/androidTest/java/com/hjq/gson/factory/test/JsonUnitTest.java @@ -7,7 +7,7 @@ import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonToken; import com.hjq.gson.factory.GsonFactory; -import com.hjq.gson.factory.JsonCallback; +import com.hjq.gson.factory.ParseExceptionCallback; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -24,6 +24,8 @@ */ public final class JsonUnitTest { + private static final String TAG = "GsonFactory"; + private Gson mGson; /** @@ -34,13 +36,32 @@ public void onTestBefore() { // CrashReport.initCrashReport(InstrumentationRegistry.getInstrumentation().getContext()); mGson = GsonFactory.getSingletonGson(); // 设置 Json 解析容错监听 - GsonFactory.setJsonCallback(new JsonCallback() { + GsonFactory.setParseExceptionCallback(new ParseExceptionCallback() { @Override - public void onTypeException(TypeToken typeToken, String fieldName, JsonToken jsonToken) { - Log.e("GsonFactory", "类型解析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken); - // 上报到 Bugly 错误列表 - // CrashReport.postCatchedException(new IllegalArgumentException("类型解析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken)); + public void onParseObjectException(TypeToken typeToken, String fieldName, JsonToken jsonToken) { + handlerGsonParseException("解析对象析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken); + } + + @Override + public void onParseListException(TypeToken typeToken, String fieldName, JsonToken listItemJsonToken) { + handlerGsonParseException("解析 List 异常:" + typeToken + "#" + fieldName + ",后台返回的条目类型为:" + listItemJsonToken); + } + + @Override + public void onParseMapException(TypeToken typeToken, String fieldName, String mapItemKey, JsonToken mapItemJsonToken) { + handlerGsonParseException("解析 Map 异常:" + typeToken + "#" + fieldName + ",mapItemKey = " + mapItemKey + ",后台返回的条目类型为:" + mapItemJsonToken); + } + + private void handlerGsonParseException(String message) { + Log.e(TAG, message); + /* + if (BuildConfig.DEBUG) { + throw new IllegalArgumentException(message); + } else { + CrashReport.postCatchedException(new IllegalArgumentException(message)); + } + */ } }); } @@ -53,7 +74,8 @@ public void parseNormalJsonTest() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); String json = getAssetsString(context, "NormalJson.json"); //mGson.toJson(mGson.fromJson(json, JsonBean.class)); - mGson.fromJson(json, JsonBean.class); + JsonBean jsonBean = mGson.fromJson(json, JsonBean.class); + Log.i(TAG, mGson.toJson(jsonBean)); } /** @@ -64,7 +86,8 @@ public void parseAbnormalJsonTest() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); String json = getAssetsString(context, "AbnormalJson.json"); //mGson.toJson(mGson.fromJson(json, JsonBean.class)); - mGson.fromJson(json, JsonBean.class); + JsonBean jsonBean = mGson.fromJson(json, JsonBean.class); + Log.i(TAG, mGson.toJson(jsonBean)); } /** @@ -74,7 +97,8 @@ public void parseAbnormalJsonTest() { public void kotlinDataClassDefaultValueTest() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); String json = getAssetsString(context, "NullJson.json"); - mGson.fromJson(json, DataClassBean.class); + DataClassBean dataClassBean = mGson.fromJson(json, DataClassBean.class); + Log.i(TAG, mGson.toJson(dataClassBean)); } /** diff --git a/build.gradle b/build.gradle index 40e8c35..cf2fb91 100644 --- a/build.gradle +++ b/build.gradle @@ -32,10 +32,25 @@ allprojects { jcenter() } - // 将构建文件统一输出到项目根目录下的 build 文件夹 - setBuildDir(new File(rootDir, "build/${path.replaceAll(':', '/')}")) + // 读取 local.properties 文件配置 + def properties = new Properties() + def localPropertiesFile = rootProject.file("local.properties") + if (localPropertiesFile.exists()) { + localPropertiesFile.withInputStream { inputStream -> + properties.load(inputStream) + } + } + + String buildDirPath = properties.getProperty("build.dir") + if (buildDirPath != null && buildDirPath != "") { + // 将构建文件统一输出到指定的目录下 + setBuildDir(new File(buildDirPath, rootProject.name + "/build/${path.replaceAll(':', '/')}")) + } else { + // 将构建文件统一输出到项目根目录下的 build 文件夹 + setBuildDir(new File(rootDir, "build/${path.replaceAll(':', '/')}")) + } } -task clean(type: Delete) { +tasks.register('clean', Delete) { delete rootProject.buildDir } \ No newline at end of file diff --git a/library/build.gradle b/library/build.gradle index 08f4e6e..b3463cf 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -5,8 +5,8 @@ android { defaultConfig { minSdkVersion 11 - versionCode 80 - versionName "8.0" + versionCode 900 + versionName "9.0" } // 使用 JDK 1.8 @@ -15,9 +15,9 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 } - android.libraryVariants.all { variant -> + android.libraryVariants.configureEach { variant -> // aar 输出文件名配置 - variant.outputs.all { output -> + variant.outputs.configureEach { output -> outputFileName = "${rootProject.name}-${android.defaultConfig.versionName}.aar" } } @@ -38,23 +38,24 @@ dependencies { } // 防止编码问题 -tasks.withType(Javadoc) { +tasks.withType(Javadoc).configureEach { options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('encoding', 'UTF-8') options.addStringOption('charSet', 'UTF-8') } -task sourcesJar(type: Jar) { +tasks.register('sourcesJar', Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } -task javadoc(type: Javadoc) { +tasks.register('javadoc', Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } -task javadocJar(type: Jar, dependsOn: javadoc) { +tasks.register('javadocJar', Jar) { + dependsOn javadoc classifier = 'javadoc' from javadoc.destinationDir } diff --git a/library/src/main/java/com/hjq/gson/factory/GsonFactory.java b/library/src/main/java/com/hjq/gson/factory/GsonFactory.java index f47ce4f..a6684f1 100644 --- a/library/src/main/java/com/hjq/gson/factory/GsonFactory.java +++ b/library/src/main/java/com/hjq/gson/factory/GsonFactory.java @@ -44,7 +44,7 @@ public final class GsonFactory { private static final List REFLECTION_ACCESS_FILTERS = new ArrayList<>(); - private static JsonCallback sJsonCallback; + private static ParseExceptionCallback sParseExceptionCallback; private static volatile Gson sGson; @@ -75,15 +75,15 @@ public static void setSingletonGson(Gson gson) { /** * 设置 Json 解析出错回调对象 */ - public static void setJsonCallback(JsonCallback callback) { - GsonFactory.sJsonCallback = callback; + public static void setParseExceptionCallback(ParseExceptionCallback callback) { + GsonFactory.sParseExceptionCallback = callback; } /** - * 获取 Json 解析出错回调对象 + * 获取 Json 解析出错回调对象(可能为空) */ - public static JsonCallback getJsonCallback() { - return sJsonCallback; + public static ParseExceptionCallback getParseExceptionCallback() { + return sParseExceptionCallback; } /** diff --git a/library/src/main/java/com/hjq/gson/factory/JsonCallback.java b/library/src/main/java/com/hjq/gson/factory/JsonCallback.java deleted file mode 100644 index 231ea6b..0000000 --- a/library/src/main/java/com/hjq/gson/factory/JsonCallback.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.hjq.gson.factory; - -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonToken; - -/** - * author : Android 轮子哥 - * github : https://github.com/getActivity/GsonFactory - * time : 2021/02/02 - * desc : Json 解析异常监听器 - */ -public interface JsonCallback { - - /** - * 类型解析异常 - * - * @param typeToken 类型 Token - * @param fieldName 字段名称(可能为空) - * @param jsonToken 后台给定的类型 - */ - void onTypeException(TypeToken typeToken, String fieldName, JsonToken jsonToken); -} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/gson/factory/ParseExceptionCallback.java b/library/src/main/java/com/hjq/gson/factory/ParseExceptionCallback.java new file mode 100644 index 0000000..f388784 --- /dev/null +++ b/library/src/main/java/com/hjq/gson/factory/ParseExceptionCallback.java @@ -0,0 +1,41 @@ +package com.hjq.gson.factory; + +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonToken; + +/** + * author : Android 轮子哥 + * github : https://github.com/getActivity/GsonFactory + * time : 2021/02/02 + * desc : Json 解析异常回调类 + */ +public interface ParseExceptionCallback { + + /** + * 对象类型解析异常 + * + * @param typeToken 类型 Token + * @param fieldName 字段名称(可能为空) + * @param jsonToken 后台给定的类型 + */ + void onParseObjectException(TypeToken typeToken, String fieldName, JsonToken jsonToken); + + /** + * List 类型解析异常 + * + * @param typeToken 类型 Token + * @param fieldName 字段名称(可能为空) + * @param listItemJsonToken List 条目类型 + */ + void onParseListException(TypeToken typeToken, String fieldName, JsonToken listItemJsonToken); + + /** + * Map 类型解析异常 + * + * @param typeToken 类型 Token + * @param fieldName 字段名称(可能为空) + * @param mapItemKey Map 集合中的 key 值,如果等于为 "null" 字符串,则证明后端返回了错误类型的 key 过来 + * @param mapItemJsonToken Map 条目类型 + */ + void onParseMapException(TypeToken typeToken, String fieldName, String mapItemKey, JsonToken mapItemJsonToken); +} \ No newline at end of file diff --git a/library/src/main/java/com/hjq/gson/factory/data/BigDecimalTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/BigDecimalTypeAdapter.java index 7560184..42d7a9a 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/BigDecimalTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/BigDecimalTypeAdapter.java @@ -2,8 +2,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import java.io.IOException; import java.math.BigDecimal; @@ -17,7 +17,8 @@ public class BigDecimalTypeAdapter extends TypeAdapter { @Override public BigDecimal read(JsonReader in) throws IOException { - switch (in.peek()) { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { case NUMBER: case STRING: String result = in.nextString(); @@ -30,7 +31,7 @@ public BigDecimal read(JsonReader in) throws IOException { return null; default: in.skipValue(); - return null; + throw new IllegalArgumentException("The current parser is of type BigDecimal, but the data is of type " + jsonToken); } } diff --git a/library/src/main/java/com/hjq/gson/factory/data/BooleanTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/BooleanTypeAdapter.java index faaa4d5..9cc4b43 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/BooleanTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/BooleanTypeAdapter.java @@ -2,8 +2,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import java.io.IOException; /** @@ -16,12 +16,20 @@ public class BooleanTypeAdapter extends TypeAdapter { @Override public Boolean read(JsonReader in) throws IOException { - switch (in.peek()) { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { case BOOLEAN: return in.nextBoolean(); case STRING: + String result = in.nextString(); // 如果后台返回 "true" 或者 "TRUE",则处理为 true,否则为 false - return Boolean.parseBoolean(in.nextString()); + if (String.valueOf(true).equalsIgnoreCase(result)) { + return true; + } else if (String.valueOf(false).equalsIgnoreCase(result)) { + return false; + } + // 如果是其他类型,则直接不解析,并且抛给上一层做处理 + throw new IllegalArgumentException("Data parsing failed, unable to convert " + result + " to boolean type"); case NUMBER: // 如果后台返回的是非 0 的数值则处理为 true,否则为 false return in.nextInt() != 0; @@ -30,7 +38,7 @@ public Boolean read(JsonReader in) throws IOException { return null; default: in.skipValue(); - throw new IllegalArgumentException(); + throw new IllegalArgumentException("The current parser is of type Boolean, but the data is of type " + jsonToken); } } diff --git a/library/src/main/java/com/hjq/gson/factory/data/DoubleTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/DoubleTypeAdapter.java index 4d0bc2e..c145859 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/DoubleTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/DoubleTypeAdapter.java @@ -2,8 +2,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import java.io.IOException; /** @@ -16,7 +16,8 @@ public class DoubleTypeAdapter extends TypeAdapter { @Override public Double read(JsonReader in) throws IOException { - switch (in.peek()) { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { case NUMBER: return in.nextDouble(); case STRING: @@ -30,7 +31,7 @@ public Double read(JsonReader in) throws IOException { return null; default: in.skipValue(); - throw new IllegalArgumentException(); + throw new IllegalArgumentException("The current parser is of type Double, but the data is of type " + jsonToken); } } diff --git a/library/src/main/java/com/hjq/gson/factory/data/FloatTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/FloatTypeAdapter.java index 5328fc5..7d974bd 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/FloatTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/FloatTypeAdapter.java @@ -2,8 +2,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import java.io.IOException; /** @@ -16,7 +16,8 @@ public class FloatTypeAdapter extends TypeAdapter { @Override public Float read(JsonReader in) throws IOException { - switch (in.peek()) { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { case NUMBER: return (float) in.nextDouble(); case STRING: @@ -30,7 +31,7 @@ public Float read(JsonReader in) throws IOException { return null; default: in.skipValue(); - throw new IllegalArgumentException(); + throw new IllegalArgumentException("The current parser is of type Float, but the data is of type " + jsonToken); } } diff --git a/library/src/main/java/com/hjq/gson/factory/data/IntegerTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/IntegerTypeAdapter.java index 9c1a61d..ab51b05 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/IntegerTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/IntegerTypeAdapter.java @@ -2,8 +2,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import java.io.IOException; import java.math.BigDecimal; @@ -17,7 +17,8 @@ public class IntegerTypeAdapter extends TypeAdapter { @Override public Integer read(JsonReader in) throws IOException { - switch (in.peek()) { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { case NUMBER: try { return in.nextInt(); @@ -41,7 +42,7 @@ public Integer read(JsonReader in) throws IOException { return null; default: in.skipValue(); - throw new IllegalArgumentException(); + throw new IllegalArgumentException("The current parser is of type Integer, but the data is of type " + jsonToken); } } diff --git a/library/src/main/java/com/hjq/gson/factory/data/JSONArrayTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/JSONArrayTypeAdapter.java index 830087a..e3d86bf 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/JSONArrayTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/JSONArrayTypeAdapter.java @@ -5,12 +5,10 @@ import com.google.gson.internal.bind.TypeAdapters; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; - +import java.io.IOException; import org.json.JSONArray; import org.json.JSONException; -import java.io.IOException; - /** * author : Android 轮子哥 * github : https://github.com/getActivity/GsonFactory @@ -19,11 +17,11 @@ */ public class JSONArrayTypeAdapter extends TypeAdapter { - public TypeAdapter mProxy = TypeAdapters.JSON_ELEMENT; + private static final TypeAdapter PROXY = TypeAdapters.JSON_ELEMENT; @Override public JSONArray read(JsonReader in) throws IOException { - JsonElement read = mProxy.read(in); + JsonElement read = PROXY.read(in); if (read.isJsonArray()) { try { return new JSONArray(read.toString()); @@ -40,6 +38,6 @@ public void write(JsonWriter out, JSONArray value) throws IOException { out.nullValue(); return; } - mProxy.write(out, mProxy.fromJson(value.toString())); + PROXY.write(out, PROXY.fromJson(value.toString())); } } \ No newline at end of file diff --git a/library/src/main/java/com/hjq/gson/factory/data/JSONObjectTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/JSONObjectTypeAdapter.java index 5c6a011..e39d59e 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/JSONObjectTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/JSONObjectTypeAdapter.java @@ -5,12 +5,10 @@ import com.google.gson.internal.bind.TypeAdapters; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; - +import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; -import java.io.IOException; - /** * author : Android 轮子哥 * github : https://github.com/getActivity/GsonFactory @@ -19,11 +17,11 @@ */ public class JSONObjectTypeAdapter extends TypeAdapter { - public TypeAdapter mProxy = TypeAdapters.JSON_ELEMENT; + private static final TypeAdapter PROXY = TypeAdapters.JSON_ELEMENT; @Override public JSONObject read(JsonReader in) throws IOException { - JsonElement read = mProxy.read(in); + JsonElement read = PROXY.read(in); if (read.isJsonObject()) { try { return new JSONObject(read.toString()); @@ -40,6 +38,6 @@ public void write(JsonWriter out, JSONObject value) throws IOException { out.nullValue(); return; } - mProxy.write(out, mProxy.fromJson(value.toString())); + PROXY.write(out, PROXY.fromJson(value.toString())); } } \ No newline at end of file diff --git a/library/src/main/java/com/hjq/gson/factory/data/LongTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/LongTypeAdapter.java index b7b37bf..d2d6d9a 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/LongTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/LongTypeAdapter.java @@ -2,8 +2,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import java.io.IOException; import java.math.BigDecimal; @@ -17,7 +17,8 @@ public class LongTypeAdapter extends TypeAdapter { @Override public Long read(JsonReader in) throws IOException { - switch (in.peek()) { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { case NUMBER: try { return in.nextLong(); @@ -41,7 +42,7 @@ public Long read(JsonReader in) throws IOException { return null; default: in.skipValue(); - throw new IllegalArgumentException(); + throw new IllegalArgumentException("The current parser is of type Long, but the data is of type " + jsonToken); } } diff --git a/library/src/main/java/com/hjq/gson/factory/data/StringTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/data/StringTypeAdapter.java index c8b7ced..c544011 100644 --- a/library/src/main/java/com/hjq/gson/factory/data/StringTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/data/StringTypeAdapter.java @@ -2,8 +2,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; - import java.io.IOException; /** @@ -16,7 +16,8 @@ public class StringTypeAdapter extends TypeAdapter { @Override public String read(JsonReader in) throws IOException { - switch (in.peek()) { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { case STRING: case NUMBER: return in.nextString(); @@ -28,7 +29,7 @@ public String read(JsonReader in) throws IOException { return null; default: in.skipValue(); - throw new IllegalArgumentException(); + throw new IllegalArgumentException("The current parser is of type String, but the data is of type " + jsonToken); } } diff --git a/library/src/main/java/com/hjq/gson/factory/element/CollectionTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/element/CollectionTypeAdapter.java index 6c7ea68..69b0f33 100644 --- a/library/src/main/java/com/hjq/gson/factory/element/CollectionTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/element/CollectionTypeAdapter.java @@ -8,8 +8,7 @@ import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import com.hjq.gson.factory.GsonFactory; -import com.hjq.gson.factory.JsonCallback; - +import com.hjq.gson.factory.ParseExceptionCallback; import java.io.IOException; import java.lang.reflect.Type; import java.util.Collection; @@ -41,28 +40,40 @@ public void setReflectiveType(TypeToken typeToken, String fieldName) { @Override public Collection read(JsonReader in) throws IOException { JsonToken jsonToken = in.peek(); + Collection collection = mObjectConstructor.construct(); if (jsonToken == JsonToken.NULL) { in.nextNull(); - return null; + return collection; } if (jsonToken != JsonToken.BEGIN_ARRAY) { in.skipValue(); - JsonCallback callback = GsonFactory.getJsonCallback(); + ParseExceptionCallback callback = GsonFactory.getParseExceptionCallback(); if (callback != null) { - callback.onTypeException(mTypeToken, mFieldName, jsonToken); + callback.onParseObjectException(mTypeToken, mFieldName, jsonToken); } - return null; + return collection; } - Collection collection = mObjectConstructor.construct(); in.beginArray(); while (in.hasNext()) { - E instance = mElementTypeAdapter.read(in); - collection.add(instance); + JsonToken itemJsonToken = null; + try { + // 获取 item 条目的类型 + itemJsonToken = in.peek(); + E instance = mElementTypeAdapter.read(in); + collection.add(instance); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + ParseExceptionCallback callback = GsonFactory.getParseExceptionCallback(); + if (callback != null) { + callback.onParseListException(mTypeToken, mFieldName, itemJsonToken); + } + } } in.endArray(); + return collection; } diff --git a/library/src/main/java/com/hjq/gson/factory/element/MapTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/element/MapTypeAdapter.java index 71e78cd..3b1a3fa 100644 --- a/library/src/main/java/com/hjq/gson/factory/element/MapTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/element/MapTypeAdapter.java @@ -3,7 +3,6 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.internal.JsonReaderInternalAccess; import com.google.gson.internal.ObjectConstructor; @@ -13,8 +12,7 @@ import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import com.hjq.gson.factory.GsonFactory; -import com.hjq.gson.factory.JsonCallback; - +import com.hjq.gson.factory.ParseExceptionCallback; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; @@ -55,13 +53,13 @@ public void setReflectiveType(TypeToken typeToken, String fieldName) { @Override public Map read(JsonReader in) throws IOException { JsonToken jsonToken = in.peek(); + Map map = mConstructor.construct(); + if (jsonToken == JsonToken.NULL) { in.nextNull(); - return null; + return map; } - Map map = mConstructor.construct(); - if (jsonToken == JsonToken.BEGIN_ARRAY) { in.beginArray(); while (in.hasNext()) { @@ -73,9 +71,9 @@ public Map read(JsonReader in) throws IOException { in.endArray(); } else { in.skipValue(); - JsonCallback callback = GsonFactory.getJsonCallback(); + ParseExceptionCallback callback = GsonFactory.getParseExceptionCallback(); if (callback != null) { - callback.onTypeException(mTypeToken, mFieldName, jsonToken); + callback.onParseObjectException(mTypeToken, mFieldName, jsonToken); } } } @@ -84,19 +82,28 @@ public Map read(JsonReader in) throws IOException { in.beginObject(); while (in.hasNext()) { JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in); - K key = mKeyTypeAdapter.read(in); - V value = mValueTypeAdapter.read(in); - V replaced = map.put(key, value); - if (replaced != null) { - throw new JsonSyntaxException("duplicate key: " + key); + JsonToken itemJsonToken = null; + K key = null; + try { + key = mKeyTypeAdapter.read(in); + // 获取 item 条目的类型 + itemJsonToken = in.peek(); + V value = mValueTypeAdapter.read(in); + map.put(key, value); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + ParseExceptionCallback callback = GsonFactory.getParseExceptionCallback(); + if (callback != null) { + callback.onParseMapException(mTypeToken, mFieldName, String.valueOf(key), itemJsonToken); + } } } in.endObject(); } else { in.skipValue(); - JsonCallback callback = GsonFactory.getJsonCallback(); + ParseExceptionCallback callback = GsonFactory.getParseExceptionCallback(); if (callback != null) { - callback.onTypeException(mTypeToken, mFieldName, jsonToken); + callback.onParseObjectException(mTypeToken, mFieldName, jsonToken); } } return map; @@ -122,7 +129,7 @@ public void write(JsonWriter out, Map map) throws IOException { boolean hasComplexKeys = false; List keys = new ArrayList<>(map.size()); - List values = new ArrayList(map.size()); + List values = new ArrayList<>(map.size()); for (Map.Entry entry : map.entrySet()) { JsonElement keyElement = mKeyTypeAdapter.toJsonTree(entry.getKey()); keys.add(keyElement); diff --git a/library/src/main/java/com/hjq/gson/factory/element/ReflectiveTypeAdapter.java b/library/src/main/java/com/hjq/gson/factory/element/ReflectiveTypeAdapter.java index 17b1d57..6210ab9 100644 --- a/library/src/main/java/com/hjq/gson/factory/element/ReflectiveTypeAdapter.java +++ b/library/src/main/java/com/hjq/gson/factory/element/ReflectiveTypeAdapter.java @@ -8,8 +8,7 @@ import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import com.hjq.gson.factory.GsonFactory; -import com.hjq.gson.factory.JsonCallback; - +import com.hjq.gson.factory.ParseExceptionCallback; import java.io.IOException; import java.util.Map; @@ -48,9 +47,9 @@ public T read(JsonReader in) throws IOException { if (jsonToken != JsonToken.BEGIN_OBJECT) { in.skipValue(); - JsonCallback callback = GsonFactory.getJsonCallback(); + ParseExceptionCallback callback = GsonFactory.getParseExceptionCallback(); if (callback != null) { - callback.onTypeException(mTypeToken, mFieldName, jsonToken); + callback.onParseObjectException(mTypeToken, mFieldName, jsonToken); } return null; } @@ -74,9 +73,10 @@ public T read(JsonReader in) throws IOException { } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (IllegalArgumentException e) { - JsonCallback callback = GsonFactory.getJsonCallback(); + e.printStackTrace(); + ParseExceptionCallback callback = GsonFactory.getParseExceptionCallback(); if (callback != null) { - callback.onTypeException(TypeToken.get(instance.getClass()), field.getFieldName(), peek); + callback.onParseObjectException(TypeToken.get(instance.getClass()), field.getFieldName(), peek); } } }