编码字典而不在 Swift 中添加编码键枚举

Encode dictionary without adding the coding key enum in Swift

我想编码一个 JSON 可以是

{"hw1":{"get_trouble":true},"seq":2,"session_id":1}


{"hw2":{"get_trouble":true},"seq":3,"session_id":2}

用于编码的 class 如下所示

class Request: Codable {
    let sessionId, seq:Int
    let content:[String:Content]
    
    enum CodingKeys:String, CodingKey{
        case sessionId = "session_id"
        case seq
        case content
    }
    
    init(sessionId:Int, seq:Int, content:[String:Content]) {
        self.sessionId = sessionId
        self.seq = seq
        self.content = content
    }
}

class Content:Codable{
    let getTrouble = true
    
    enum CodingKeys:String, CodingKey {
        case getTrouble = "get_trouble"
    }
}

如何对请求进行编码以便获得所需的结果?目前,如果我这样做

let request = Request(sessionId: session, seq: seq, content: [type:content])
let jsonData = try! encoder.encode(request)

我明白了

{"content":{"hw1":{"get_trouble":true}},"seq":2,"session_id":1}

而且我不希望 JSON 中有“内容”。已经调查
Swift Codable: encode structure with dynamic keys 并且无法弄清楚如何在我的用例中应用

与几乎所有自定义编码问题一样,您需要的工具是 AnyStringKey(这让我很沮丧,因为它不在 stdlib 中):

struct AnyStringKey: CodingKey, Hashable, ExpressibleByStringLiteral {
    var stringValue: String
    init(stringValue: String) { self.stringValue = stringValue }
    init(_ stringValue: String) { self.init(stringValue: stringValue) }
    var intValue: Int?
    init?(intValue: Int) { return nil }
    init(stringLiteral value: String) { self.init(value) }
}

这只是让您对任意键进行编码和编码。有了这个,编码器就很简单了:

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: AnyStringKey.self)
    for (key, value) in content {
        try container.encode(value, forKey: AnyStringKey(key))
    }
    try container.encode(sessionId, forKey: AnyStringKey("session_id"))
    try container.encode(seq, forKey: AnyStringKey("seq"))
}

这假定您的意思是在内容中允许多个 key/value 对。我希望你不会;您只是在使用字典,因为您想要一种更好的编码方式。如果 Content 有一个键,那么你可以这样更自然地重写它:

// Content only encodes getTrouble; it doesn't encode key
struct Content:Codable{
    let key: String
    let getTrouble: Bool

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(["get_trouble": getTrouble])
    }
}

struct Request: Codable {
    // ...

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: AnyStringKey.self)
        try container.encode(content, forKey: AnyStringKey(content.key))
        try container.encode(sessionId, forKey: AnyStringKey("session_id"))
        try container.encode(seq, forKey: AnyStringKey("seq"))
    }
}

现在这可能仍然困扰您,因为它将部分内容编码逻辑推入了 Request。 (好吧,也许这只是困扰我。)如果你暂时搁置 Codable,你也可以解决它。

// Encode Content directly into container
extension KeyedEncodingContainer where K == AnyStringKey {
    mutating func encode(_ value: Content) throws {
        try encode(["get_trouble": value.getTrouble], forKey: AnyStringKey(value.key))
    }
}


struct Request: Codable {
    // ...

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: AnyStringKey.self)

        // And encode into the container (note no "forKey")
        try container.encode(content)

        try container.encode(sessionId, forKey: AnyStringKey("session_id"))
        try container.encode(seq, forKey: AnyStringKey("seq"))
    }
}