jsonkotlinreleaseserializable

Why the Json.decodeFromString does not work in the Release version on android app


I am receiving a response from the server as json and want to convert it to an array using kotlinx.serialization.json.Json.decodeFromString. The code works fine in the Debag version, but it crashes in the Release version. Help, please, what am I doing wrong?

My code in class ActivityPro

@Serializable
data class PaymentResult(val id: String, val data: String)

@Serializable
data class PaymentRequest(val result: PaymentResult, val errorCode: Int, val errorMessage: String)

...
val paymentRequest = Json.decodeFromString<PaymentRequest>(responseString)
                    

responseString

{"result":{"id":"2ad333b0-000f-5000-8000-1ec5665f4757","data":"https:\/\/3ds-gate.yoomoney.ru\/card-auth?acsUri=https%3A%2F%2F3ds-ds2.mirconnect.ru%3A443%2Fsc1%2Fpareq&MD=4399694261676&PaReq=eJxVUU1zgjAQ%2FSuOxx5ICKQaZ82MlkM9xFFrDz1mwo4yKmCAov76Jgq1PWXf24%2B8fQvbvUVMPtA0FiUorCq9w0GWToerzZpFPKKh4GIoYTXb4FnCN9oqK3IZBjRgQHroOq3Z67yWoM15vlhKHlIaj4B0EE5oF4kUTPB4zFhIgTwYyPUJ5Zd6KY%2F6CuSOwBRNXturfI0jID2Axh7lvq7LakLItSgO2okNbAPEJ4A8JawaH1Vu0CVLpUpm7fK2aJdbw1SiWnVbc3U7tGq7mwLxFZDqGiWjXhcdD%2BhoQumEhkDuPOiTVyA3n%2FOBoAF10jsGSv%2FR7AEE9Zm%2FDDhTLeam36NHgJeyyNFVOAd%2FY0ixMm6N7nnu8PbuTTW1MysecR7FXIjIG3un%2FLDMucPi8DHNAyC%2BiXQ3I91ZXfTv3D8Xnqa1&TermUrl=https%3A%2F%2Fpaymentcard.yoomoney.ru%3A443%2F3ds%2Fchallenge%2F279%2FsU5z3DrCkvZJlGEBl2rAYmgE45QZ..001.202210"},"errorCode":0,"errorMessage":""}

error log

2022-10-08 10:35:08.894 27772-27772/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: ru.keytomyself.customeraccounting, PID: 27772
jb.p0: Unresolved class: class ru.keytomyself.customeraccounting.ActivityPro$a
    at jb.l$a$i.invoke(SourceFile:32)
    at jb.r0$a.invoke(SourceFile:2)
    at jb.l$a.a(SourceFile:1)
    at jb.l.C(Unknown Source:8)
    at jb.l.getDescriptor(Unknown Source:0)
    at jb.s0.i(SourceFile:1)
    at ab.c0.d(Unknown Source:10)
    at ze.a0$b.invokeSuspend(SourceFile:2)
    at ta.a.resumeWith(Unknown Source:8)
    at pd.o0.run(Unknown Source:86)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:236)
    at android.app.ActivityThread.main(ActivityThread.java:8057)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)

I will be grateful for any help


Solution

  • The most likely cause of this is that you have enabled code minification/obfuscation for your release build in your build.gradle. After obfuscation your class will be looking something like this:

    @Serializable
    data class ABC(val z: String, val zz: String) // PaymentResult class
    

    Now if your try to decode the string {"id":"2ad333b0", "data": "https:"} into this data class, it will fail because the class has no id property, it has been renamed to z.

    To prevent this from happening, one solution is to add @SerialName annotation to all the fields in your serializable classes. For example,

    @Serializable
    data class PaymentResult(
        @SerialName("id")
        val id: String,
        @SerialName("data") 
        val data: String
    )
    

    Now instead of using property name, the library will now use the provided SerialName while decoding the json and these strings aren't obfuscated.

    Another solution is to disable obfuscation for these serializable classes. You can use the @Keep annotation to exclude a particular class/function from being obfuscated/minified.

    @Keep
    @Serializable
    data class PaymentResult(val id: String, val data: String)
    
    @Keep
    @Serializable
    data class PaymentRequest(val result: PaymentResult, val errorCode: Int, val errorMessage: String)
    

    The other way is to add rules to proguard-rules.pro file to exclude certain classes/directories from being obfuscated. Checkout the following resources on how to write those rules:

    https://www.guardsquare.com/manual/configuration/usage#keepoptions

    How to keep/exclude a particular package path when using proguard?