Gson().fromJson 出错 - "Failed to invoke public com.keikakupet.PetStatus() with no args"

Error with Gson().fromJson - "Failed to invoke public com.keikakupet.PetStatus() with no args"

我正在尝试使用 Gson 库将我的 Kotlin class 实例存储到 JSON 文件中。但是,当我 运行 Gson().fromJson 时,我收到以下错误:

java.lang.RuntimeException: Failed to invoke public com.keikakupet.PetStatus() with no args

我对错误的理解是 Gson 要求我的 class 有一个不带参数的主构造函数,因此它可以构造所需的对象(在本例中为 PetStatus 对象)。但是,我有这样一个构造函数。我不确定问题的一部分是否出在我 运行 从 init 中调用方法这一事实。有谁知道我该如何解决这个错误?

我的代码:

package com.keikakupet

import android.content.Context
import android.util.Log
import com.google.gson.Gson
import java.io.File
import java.util.*
import java.io.BufferedReader

class PetStatus constructor(){

    var maxHealth: Int = 10
    var currentHealth: Int = 10
    var healthIncrementer: Int = 2 // amount by which health increments when a task is completed
    var healthDecrementer: Int = 0 // amount by which health decrements when user approaches / misses deadline
    var isHungry: Boolean = false
    var isTired: Boolean = false
    var isSick: Boolean = false
    var isAlive: Boolean = true

    init{
        //if a json exists, use it to update PetStatus
        val context = getContext()
        var file = File(context.getFilesDir(), "PetStatus.json")
        if(file.exists()){
            Log.d("FILE_EXISTS", "File exists!")

            val bufferedReader: BufferedReader = file.bufferedReader()
            val json = bufferedReader.readText()

            val retrievedStatus = Gson().fromJson(json, PetStatus::class.java)

            Log.d("JSON_RETRIEVED", json)
        }
        else
            Log.d("FILE_DNE", "File does not exist!")
            updateJson()
    }

    // method to update pet's health and ailment upon completing a task
    fun processCompletedTask(){
        incrementHealth()
        removeAilment()
        Log.d("TASK_COMPLETE", "completed task processed")
    }

    fun getHealthPercent(): Int {
        return currentHealth / maxHealth
    }

    // method to update pet's health and ailment upon missing a task
    fun processMissedTask(){
        decrementHealth()
        addAilment()
        Log.d("TASK_MISSED", "missed task processed")
    }

    /*
    Potentially creating another method to update pet's
    health and status because of an approaching deadline.
     */

    // method to decrement the pet's health
    private fun decrementHealth(){
        currentHealth-=healthDecrementer
        if(currentHealth <= 0)
            isAlive = false
        updateJson()
    }

    // method to increment the pet's health
    private fun incrementHealth(){
        val sum = currentHealth + healthIncrementer
        if(sum > maxHealth)
            currentHealth = maxHealth
        else
            currentHealth = sum
        updateJson()
    }

    // method to add an ailment to the pet
    private fun addAilment(){
        // if no ailment, randomly assign hungry or tired
        if(!isHungry && !isTired && !isSick){
            val rand = Random()
            val randBool = rand.nextBoolean()
            if(randBool)
                isHungry = true
            else
                isTired = true
            healthDecrementer = 1
        }

        // otherwise, if hungry XOR tired, assign the other
        else if(isHungry && !isTired){
            isTired = true
            healthDecrementer = 2
        }
        else if(isTired && !isHungry){
            isHungry = true
            healthDecrementer = 2
        }

        // otherwise, if both hungry AND tired, assign sick
        else if(isHungry && isTired){
            isSick = true
            healthDecrementer = 3
        }

        updateJson()
    }

    // method to remove an ailment from the pet
    private fun removeAilment(){
        // if sick, remove sick
        if(isSick){
            isSick = false
            healthDecrementer = 2
        }

        // otherwise, if hungry and tired, remove one of the two randomly
        else if(isHungry && isTired){
            val rand = Random()
            val randBool = rand.nextBoolean()
            if(randBool)
                isHungry = false
            else
                isTired = false
            healthDecrementer = 1
        }

        // otherwise, if hungry XOR tired, remove relevant ailment
        else if(isHungry && !isTired){
            isHungry = false
            healthDecrementer = 0
        }
        else if(isTired){
            isTired = false
            healthDecrementer = 0
        }

        updateJson()
    }

    private fun updateJson(){
        val gson = Gson()
        var json: String = gson.toJson(this)
        Log.d("JSON_UPDATE", json)

        val context = getContext()
        var file = File(context.getFilesDir(), "PetStatus.json")
        file.writeText(json)

        val bufferedReader: BufferedReader = file.bufferedReader()
        json = bufferedReader.readText()
        Log.d("JSON_FROM_FILE", json)
    }

    companion object {

        private lateinit var context: Context

        fun setContext(con: Context) {
            context=con
        }

        fun getContext() : Context {
            return context
        }
    }
}

Logcat 信息:

Caused by: java.lang.RuntimeException: Failed to invoke public com.keikakupet.PetStatus() with no args
        at com.google.gson.internal.ConstructorConstructor.construct(ConstructorConstructor.java:118)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212)
        at com.google.gson.Gson.fromJson(Gson.java:927)
        at com.google.gson.Gson.fromJson(Gson.java:892)
        at com.google.gson.Gson.fromJson(Gson.java:841)
        at com.google.gson.Gson.fromJson(Gson.java:813)
        at com.keikakupet.PetStatus.<init>(PetStatus.kt:32)
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
        at com.google.gson.internal.ConstructorConstructor.construct(ConstructorConstructor.java:110)
         ... 4981 more

由于您的构造函数没有参数,Gson 无法在 json 的帮助下实例化您的 class。

像这样重组你的 class:

class PetStatus private constructor(
    var maxHealth: Int = 10,
    var currentHealth: Int = 10,
    var healthIncrementer: Int = 2, // amount by which health increments when a task is completed
    var healthDecrementer: Int = 0, // amount by which health decrements when user approaches / misses deadline
    var isHungry: Boolean = false,
    var isTired: Boolean = false,
    var isSick: Boolean = false,
    var isAlive: Boolean = true) {

    /**
     * This is optional fix, since it is a design guideline
     * recommendation, you can retain your original function as well
     * fun getHealthPercent(): Int {
     *     return currentHealth / maxHealth
     * }
     */
    val healthPercent: Int
        get() = currentHealth / maxHealth

    ...

    companion object {

        lateinit var context: Context // getters and setters for java are automatically generated

        operator fun invoke(): PetStatus {
            //if a json exists, use it to update PetStatus
            val context = context
            var file = File(context.getFilesDir(), "PetStatus.json")
            if(file.exists()){
                Log.d("FILE_EXISTS", "File exists!")

                val bufferedReader: BufferedReader = file.bufferedReader()
                val json = bufferedReader.readText()

                val retrievedStatus = Gson().fromJson(json, PetStatus::class.java)

                Log.d("JSON_RETRIEVED", json)

                return retrievedStatus
            } else {
                Log.d("FILE_DNE", "File does not exist!")
                return PetStatus()
            }
            updateJson()
        }

        operator fun invoke(maxHealth: Int, currentHealth: Int, healthIncrementer: Int, healthDecrementer: Int, isHungry: Boolean, isTired: Boolean, isSick: Boolean, isAlive: Boolean): PetStatus
            = PetStatus(maxHealth, currentHealth, healthIncrementer, healthDecrementer, isHungry, isTired, isSick, isAlive)
    }
}

现在您可以调用 class 相同的

PetStatus()

operator fun invoke() 是一种 hack(ish)方法,我建议您实际取出代码并从外部实例化 class。