具有自定义结构的Swift JSONEncoder编码通用类型

时间:2020-08-15 22:33:43

标签: json swift generics encoding

我目前正在尝试使用T对具有这种类型属性的通用结构(JSONEncoder)进行编码:

struct A <T:Codable> : Codable {
    var id: Int
    var attribute : T
    
    init(id: Int, attribute: T){
        self.id = id
        self.attribute = attribute
    }
    
}

struct B : Codable {
    var name: String
    var age: Int
}
let encoder = JSONEncoder()
let foo = A<B>(id: 1, attribute: B(name: "name", age: 29))

try? encoder.encode(foo)

这将导致如下所示的JSON:

{
  "id" : 1,
  "attribute" : {
    "name" : "name",
    "age" : 29
  }
}

但是我想自定义编码以使嵌套属性达到根级别:

{
  "id" : 1,
  "name" : "name",
  "age" : 29
}

使用自定义CodingKey结构对我不起作用,因为T可能具有任意数量的属性,并且键(属性名称)事先未知。

1 个答案:

答案 0 :(得分:3)

这需要通过实现encode(to:)init(from:)来手动完成:

struct A <T:Codable> {
    var id: Int
    var attribute : T
}
extension A: Codable {
    enum CodingKeys: CodingKey { case id }

    func encode(to encoder: Encoder) throws {
       // use attribute's own encode(to:)
       try attribute.encode(to: encoder) 

       // then encode the id
       var container = encoder.container(keyedBy: CodingKeys.self)
       try container.encode(id, forKey: .id)
    }

    init(from decoder: Decoder) throws {
        // use attribute's own init(from:)
        self.attribute = try T.init(from: decoder)
        
        // decode the id
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
    }
}

请注意,这是一个非常脆弱的解决方案。我不建议按照您的计划进行编码。

如果EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)的编码容器与T的编码容器(是键容器< / em>)

例如,以下内容在运行时失败:

A

这是因为当let a = A(id: 1, attribute: "A") let data = JSONEncoder().encode(a) T时,其容器是String。如果SingleValueEncodingContainer是一个数组,也会发生同样的情况:

T

因为数组是用let a = A(id: 1, attribute: ["A"]) 编码的