当超类也是 Codable 时,如何在子类中使用 CodingKeys?

Posted

技术标签:

【中文标题】当超类也是 Codable 时,如何在子类中使用 CodingKeys?【英文标题】:How to use CodingKeys in sub-class when the super class is also Codable? 【发布时间】:2019-07-09 09:01:15 【问题描述】:

我正在做 JSON 编码和解码,但是有些问题很烦人,我不知道如何在继承类中使用 CodingKeys

我有两个课程ResponseBeanResponseWithObjectBean<T>

这里是响应类定义:

public class ResponseBean: Codable

    //This is only sample, I define `CodingKeys` because the property in json is in different name.
    private enum CodingKeys: String, CodingKey
    
        case intA
        case intB
    
    public var intA: Int32 = 0
    public var intB: Int32 = 0



public class ResponseWithObjectBean<T: Codable> : ResponseBean

    /*
    Here I don't know how to define an enum to confirm protocl CondingKey. 
    I defined an enum named CodingKeys or whatever, they just don't work and 
    the testMessage and obj are still nil.
    But if I implement the init(from decoder: Decoder) construction and manually
    pass the coding keys which I defined to the decode function, all works fine.
    */
    public var testMessage: String? = nil
    public var obj: T? = nil

我会从响应中得到一个用户:

public class User: Codable

    private enum CodingKeys: String, CodingKey
    
        case name
        case age
    

    public var name: String? = nil
    public var age: Int32? = nil

这里是测试json:

var testJson = """

    "intA": 10,
    "intB": 20,
    "testMessage": "This is a test json",
    "obj":
        "name": "LiHong",
        "age": 11
    

"""

以下是我的运行方式:

do
    var responseData = testJson.data(using: .utf8)
    var decoder = JSONDecoder()
    var response: ResponseWithObjectBean<User> = try decoder.decode(ResponseWithObjectBean<User>.self, from: responseData)
catch let e


我不知道如何在ResponseWithObjectBean 类中定义CodingKeys,即使我做了,它也根本不起作用。但是如果我实现init(from decoder: Decoder) throws 构造并手动传递我在ResponseWithObjectBean 中定义的编码键,我可以获得所有属性。

【问题讨论】:

目前,你必须实现init(from decoder: Decoder) throws方法。 我认为这不是语言设计者想要解决这个问题的正确方法。如果子类很大,这可能会非常痛苦。 目前是这样的。编译器无法将子类一致性合成为Codable。在上述child-parent 的情况下,您别无选择,只能自己实现 initliazer。 他们如何在没有完成这些基本功能的情况下发布新版本? Swift 远非一门好的程序语言。年轻的 Kotlin 比它好得多(尽管我不喜欢 Kotlin 中的位运算符)。 【参考方案1】:

这很简单,你只需在子类中手动进行编码和解码:

public class ResponseWithObjectBean<T: Codable> : ResponseBean 
    public var testMessage: String? = nil
    public var obj: T? = nil

    // Create another CodingKey compliant enum with another name for the new keys
    private enum CustomCodingKeys: String, CodingKey 
        case testMessage
        case obj
    

    // Override the decoder
    required init(from decoder: Decoder) throws 
        try super.init(from: decoder)

        let container = try decoder.container(keyedBy: CustomCodingKeys.self)

        testMessage = try container.decode(String?.self, forKey: .testMessage)
        obj = try container.decode(T?.self, forKey: .obj)
    

    // And the coder
    public override func encode(to encoder: Encoder) throws 
        try super.encode(to: encoder)

        var container = encoder.container(keyedBy: CustomCodingKeys.self)

        try container.encode(testMessage, forKey: .testMessage)
        try container.encode(obj, forKey: .obj)
    


这样你就可以按照你想要的方式解码和编码:

let decoder = JSONDecoder()
let response = try decoder.decode(ResponseWithObjectBean<User>.self, from: responseData)

let data = try JSONEncoder().encode(response)
print(String(data: data, encoding: .utf8))

编辑:为了防止您手动编写所有这些样板,您可以使用 Sourcery 之类的生成工具:https://github.com/krzysztofzablocki/Sourcery

【讨论】:

是的,我在我的问题中提到了这一点,现在我将其用作临时解决方案。但是,如果我的子类很大,我将不乐意手动编写所有属性。而且这一定不是语言设计者想的那样解决这个问题的方法。我真的很喜欢 android 上的 Retrofit 和 RxJava。 不幸的是,这是唯一的方法。顺便说一句,这是编译器在可以合成时如何完成的。我完全理解您必须自己编写容易出错的样板的问题,但是对于此类问题,您是否考虑过 Sourcery ? github.com/krzysztofzablocki/Sourcery(我将编辑我的答案以提出它) 感谢您的回答和更新。 不客气。顺便说一句,如果答案适合你,别忘了接受。

以上是关于当超类也是 Codable 时,如何在子类中使用 CodingKeys?的主要内容,如果未能解决你的问题,请参考以下文章

在其超类也具有相同名称的 ivar 的子类中拥有 ivar“委托”

多态小记

java多态加深

javascript设计模式——装饰者模式

什么体现了类的多态性?

在java中,如何创建一个具有私有构造函数的类,其超类也有一个私有构造函数?