Moshi 错误:@JsonClass 不能应用于 [class]不得密封

问题描述

想象一下这个数据样本

    "Meta_data": [
                {
                    "id": 40097,"key": "_wcf_frm_created","value": ""
                },{
                    "id": 40098,"key": "_wcf_custom_degin_checkBox",{
                    "id": 40099,"key": "_wcf_frm_data","value": {
                        "1": {
                            "1": "","2": "","3": "chk_Box"
                        }
                    }
                },{
                    "id": 40119,"key": "_vendor_select","value": "6484"
                },{
                    "id": 40120,"key": "_vendor_percentage","value": "1"
                },{
                    "id": 40121,"key": "_vendor_pro_cat","value": "Accessories"
                }
            ]

Meta_data 中的 Value 可以有多种类型。在我使用的生成器中,数据类型应该像这样创建。

sealed class Value {
        class StringMapMapValue(val value: Map<String,Map<String,String>>) : Value()
        class StringValue(val value: String)                                 : Value()
    }

对于 moshi,我知道您必须在数据类之上添加 @JsonClass(generateAdapter = true)。因此我有这样的事情

    @JsonClass(generateAdapter = true)
    data class MetaDatum (
            val id: Long,val key: String,val value: Value
    )
    @JsonClass(generateAdapter = true)
    sealed class Value {
        class StringMapMapValue(val value: Map<String,String>>) : Value()
        class StringValue(val value: String)                                 : Value()
    }

我想指出,完整的 json 比这大得多。然而,这是我唯一的问题。我也有一些 Enum 问题,但这些问题可以用 String 替换

我收到的错误错误:@JsonClass 不能应用于 net......Activity.Value:不能被密封 公共静态抽象类值

因此我的问题是,我如何解码具有多种枚举类型的 json。

我会在这里添加这个,在 xCode(swift) 中这是我设法做到的。

enum Value: Codable {
    case string(String)
    case stringMapMap([String: [String: String]])
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode([String: [String: String]].self) {
            self = .stringMapMap(x)
            return
        }
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        throw DecodingError.typeMismatch(Value.self,DecodingError.Context(codingPath: decoder.codingPath,debugDescription: "Wrong type for Value"))
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let x):
            try container.encode(x)
        case .stringMapMap(let x):
            try container.encode(x)
        }
    }
}

调用数据

fun retrieveMenu(sku: Int,SSLAuth: String)
    {

        doAsync {

            val client = OkHttpClient().newBuilder()
                .build()
            val formBody: RequestBody = FormBody.Builder()
                .build()
            val request: Request = Request.Builder()
                .url("https://carteapp.net/..................")
                .method("Get",formBody)
                .build()
            client.newCall(request).execute().use { response ->
                if (!response.isSuccessful) throw IOException("Unexpected code $response")

                val gist =
                        gistJsonAdapter.fromJson(response.body!!.source())
                println(gist)


            }
        }

    }

private val moshi = moshi.Builder().build()
private val gistJsonAdapter = moshi.adapter(BarcodeScannerActivity.WcProductCall::class.java)
@JsonClass(generateAdapter = true)
    data class WcProductCall (
            val id: Long,...........
            val MetaData: List<MetaDatum>,...
    )

解决方法

这有点棘手,因为 Moshi 不允许密封/抽象类,我的想法是使用 DTO 模式(这是一种使用数据传输对象在单个网络请求中传递尽可能多的数据的模式)可能)

创建一个包含两个/所有数据的数据类(您的 DTO)

@JsonClass(generateAdapter = true)
data class MetaDatumDTO (
        val id: Long,val key: String,val value1: Value1DTO?,val value2: Value2DTO?
)

@JsonClass(generateAdapter = true)
data class Value1DTO(val value: Map<String,Map<String,String>>)

@JsonClass(generateAdapter = true)
data class Value2DTO(val value: String) 

然后在您的存储库/模型中,当您检索数据时,使用映射器函数将数据映射到您要使用的数据类

data class MetaDatum(
    val id: Long,val value: Value?
)

sealed class Value {
    data class Value1(val value: Map<String,String>>) : Value()
    data class Value2(val value: String) : Value()
}

fun MetaDatumDto.mapFromNetworkRequest(): MetaDatum {
    return MetaDatum(
        id = id,key = key,value = getValueFromDTO()
    )
}

fun MetaDatumDto.getValueFromDTO(): Value? {
    return (value1 as? Value1) ?: (value2 as? Value2)
}