如何使用 Gson @SerializedName 注释在 Kotlin 中反序列化嵌套的 Json API 响应
Posted
技术标签:
【中文标题】如何使用 Gson @SerializedName 注释在 Kotlin 中反序列化嵌套的 Json API 响应【英文标题】:How to deserialize nested Json API response in Kotlin using Gson @SerializedName annotations 【发布时间】:2022-01-13 15:10:35 【问题描述】:我正在将一个项目迁移到一个新的 API,因此必须更新反序列化和数据类。使用旧 API 和数据类一切正常,但使用更新版本我得到 以下错误
java.lang.ClassNotFoundException: Didn't find class "fooapp.component.ui.cafeteria.model.CafeteriaData" on path: DexPathList[[zip file "/data/app/~~Wji9DkrkAr7qFGIIJ3Aglg==/fooapp-dbYvbr8le25I8FGioIrW6w==/base.apk"],nativeLibraryDirectories=[/data/app/~~Wji9DkrkAr7qFGIIJ3Aglg==/fooapp-dbYvbr8le25I8FGioIrW6w==/lib/x86_64, /system/lib64, /system_ext/lib64]]
尝试访问嵌套字段时,例如:
response.cafeterias[0]
请注意,response.cafeterias
已正确解析且可访问。因此看起来 解析适用于嵌套级别 1,但不知何故 在进入下一级。
我知道映射中缺少 prices
字段,现在不需要它。明天我会检查将其添加到序列化层次结构中是否可以解决问题。
到目前为止我所尝试的
使缓存失效并重新启动 清理和重建项目 Disabling Hot swapping 获取一个小样本 JSON 并直接解析它,而不是让 GSON 在后台解析它 涉及的数据类中的字段的 val 和 var示例 JSON
"canteens": [
"version": "2.1",
"canteen_id": "best-canteen",
"weeks": [
"number": 48,
"year": 2021,
"days": [
"date": "2021-11-29",
"dishes": [
"name": "Tasty food 1",
"prices":
"students":
"base_price": 0,
"price_per_unit": 0.33,
"unit": "100g"
,
"staff":
"base_price": 0,
"price_per_unit": 0.55,
"unit": "100g"
,
"guests":
"base_price": 0,
"price_per_unit": 0.66,
"unit": "100g"
,
"ingredients": [
"Gl",
"GlW",
"Kn"
],
"dish_type": "Daily 4"
]
]
]
]
Proguard 规则
proguard-gson.pro
## GSON 2.2.4 specific rules ##
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
-keepattributes EnclosingMethod
# Gson specific classes
-keep class sun.misc.Unsafe *;
-keep class com.google.gson.stream.** *;
proguard-square-okhttp.pro
# OkHttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.squareup.okhttp.** *;
-keep interface com.squareup.okhttp.** *;
-dontwarn com.squareup.okhttp.**
proguard-square-okhttp3.pro
# OkHttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** *;
-keep interface okhttp3.** *;
-dontwarn okhttp3.**
proguard-square-retrofit.pro
# Retrofit 1.X
-keep class com.squareup.okhttp.** *;
-keep class retrofit.** *;
-keep interface com.squareup.okhttp.** *;
-dontwarn com.squareup.okhttp.**
-dontwarn okio.**
-dontwarn retrofit.**
-dontwarn rx.**
-keepclasseswithmembers class *
@retrofit.http.* <methods>;
# If in your rest service interface you use methods with Callback argument.
-keepattributes Exceptions
# If your rest service methods throw custom exceptions, because you've defined an ErrorHandler.
-keepattributes Signature
# Also you must note that if you are using GSON for conversion from JSON to POJO representation, you must ignore those POJO classes from being obfuscated.
# Here include the POJO's that have you have created for mapping JSON response to POJO for example.
proguard-square-retrofit2.pro
# Retrofit 2.X
## https://square.github.io/retrofit/ ##
-dontwarn retrofit2.**
-keep class retrofit2.** *;
-keepattributes Signature
-keepattributes Exceptions
-keepclasseswithmembers class *
@retrofit2.http.* <methods>;
proguard-guava.pro
# Configuration for Guava 18.0
#
# disagrees with instructions provided by Guava project: https://code.google.com/p/guava-libraries/wiki/UsingProGuardWithGuava
-keep class com.google.common.io.Resources
public static <methods>;
-keep class com.google.common.collect.Lists
public static ** reverse(**);
-keep class com.google.common.base.Charsets
public static <fields>;
-keep class com.google.common.base.Joiner
public static com.google.common.base.Joiner on(java.lang.String);
public ** join(...);
-keep class com.google.common.collect.MapMakerInternalMap$ReferenceEntry
-keep class com.google.common.cache.LocalCache$ReferenceEntry
# http://***.com/questions/9120338/proguard-configuration-for-guava-with-obfuscation-and-optimization
-dontwarn javax.annotation.**
-dontwarn javax.inject.**
-dontwarn sun.misc.Unsafe
# Guava 19.0
-dontwarn java.lang.ClassValue
-dontwarn com.google.j2objc.annotations.Weak
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Guava 20.0
-dontwarn com.google.**
# Guava 23.5
-dontwarn afu.org.checkerframework.**
-dontwarn org.checkerframework.**
proguard-project-rules.pro
# Add project specific ProGuard rules here.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
-dontobfuscate
-keep class fooapp.models.***;
proguard-simple-xml.pro
# Simple-Xml Proguard Config
# NOTE: You should also include the Android Proguard config found with the build tools:
# $ANDROID_HOME/tools/proguard/proguard-android.txt
# Keep public classes and methods.
-dontwarn com.bea.xml.stream.**
-dontwarn org.simpleframework.xml.stream.**
-keep class org.simpleframework.xml.** *;
-keepclassmembers,allowobfuscation class *
@org.simpleframework.xml.* <fields>;
@org.simpleframework.xml.* <init>(...);
proguard-sqlite.pro
-keep class org.sqlite.** *;
-keep class org.sqlite.database.** *;
proguard-square-okio.pro
# Okio
-keep class sun.misc.Unsafe *;
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
proguard-tikxml.pro
-keep class com.tickaroo.tikxml.** *;
-keep class **$$TypeAdapter *;
-keepclasseswithmembernames class *
@com.tickaroo.tikxml.* <fields>;
-keepclasseswithmembernames class *
@com.tickaroo.tikxml.* <methods>;
数据类
自助餐厅响应
data class CafeteriaResponse(
@SerializedName("canteens")
var cafeterias: List<CafeteriaData>,
)
自助餐厅数据
data class CafeteriaData(
@SerializedName("version")
var version: String? = null,
@SerializedName("canteen_id")
var cafeteriaSlug: String? = null,
@SerializedName("weeks")
var menusByWeeks: List<WeeklyMenu>
)
每周菜单
data class WeeklyMenu(
@SerializedName("number")
var weekOfYear: Short = -1,
@SerializedName("year")
var year: Short = -1,
@SerializedName("days")
var dishesForWeek: List<DailyMenu>
)
每日菜单
data class DailyMenu(
@SerializedName("date")
var date: DateTime? = null,
@SerializedName("dishes")
var dishesForDay: List<Dish>
)
菜
data class Dish(
@SerializedName("name")
var name: String? = null,
@SerializedName("ingredients")
var ingredients: List<String>,
@SerializedName("dish_type")
var type: String? = null
)
所有这些数据类都在同一个包中。
【问题讨论】:
是否在构建中启用了 Proguard?我认为不知何故该类在运行时丢失。 是的,该项目在其构建中使用了 proguard。如果我也提供 gradle buildscripts 会有帮助吗? 请发布您的 proguard 规则。 还有 4 个文件用于 appcompat 和 google play 服务,但这些文件似乎与此问题无关,如果您认为它们可能相关,请告诉我,我会再次更新。顺便说一句,谢谢您的帮助。 我已经尝试在禁用 proguard 的情况下运行该应用程序,并且肯定可以这样运行。所以它似乎确实与proguard有关。 【参考方案1】:问题
proguard-square-retrofit.pro
需要更新,包括用于 API 数据反序列化的新 POJO。
解决方案
我将所有 POJO 添加到以下 proguard 规则集。我将它们全部放在一个单独的包中,以获得更精细的混淆粒度并避免保留不必要的类。
更新了 proguard-square-retrofit.pro
# Retrofit 1.X
-keep class com.squareup.okhttp.** *;
-keep class retrofit.** *;
-keep interface com.squareup.okhttp.** *;
-dontwarn com.squareup.okhttp.**
-dontwarn okio.**
-dontwarn retrofit.**
-dontwarn rx.**
-keepclasseswithmembers class *
@retrofit.http.* <methods>;
# If in your rest service interface you use methods with Callback argument.
-keepattributes Exceptions
# If your rest service methods throw custom exceptions, because you've defined an ErrorHandler.
-keepattributes Signature
# Also you must note that if you are using GSON for conversion from JSON to POJO representation, you must ignore those POJO classes from being obfuscated.
# Here include the POJO's that have you have created for mapping JSON response to POJO for example.
-keep class fooapp.component.ui.cafeteria.model.deserialization.*
【讨论】:
我建议你使用@Keep
注解而不是手动添加proguard条目。从长远来看,这将对您有所帮助,因为有时很容易错过这些规则。以上是关于如何使用 Gson @SerializedName 注释在 Kotlin 中反序列化嵌套的 Json API 响应的主要内容,如果未能解决你的问题,请参考以下文章
Gson:@Expose 与 @SerializedName