Swift 具有动态键的可编码递归枚举

Swift codable recursive enum with dynamic keys

我正在尝试为动态数据编写结构。数据的键和它们的值都是未知的。该结构如下所示:

enum EntryData: Codable {
   case string(String)
   case array([EntryData]
   case nested([String: EntryData])
}

struct Entry: Codable {
   var data: [String: EntryData]
}

这样做的目标是能够像这样解码 JSON:

{
  "data": {
    "testA": "valueA",
    "testB": ["valueB", ["valueC"]],
    "testC": {
      "testD": "valueD",
      "testE": ["valueE", "valueF"]
    }
  }
}

并具有以下代码:

var data = EntryData(data: [
    "testA": .string("valueA"),
    "testB": .array([.string("valueB"), .array([.string("valueC")])]),
    "testC": .nested([
        "testD": .string("valueD"),
        "testeE": .array([.string("valueE"), .string("valueF")])
    ])
])

编码到上面的 JSON 输出。

这在 Swift 中可行吗?如果是这样,实现会是什么样子?

非常感谢。

您可以在 EntryData 的每种情况下使用 singleValueContainer 到 decode/encode,而无需使用任何硬编码键。解码时,我们可以把这三种情况都尝试解码,看哪一种成功。

enum EntryData: Codable {
   case string(String)
   case array([EntryData])
   case nested([String: EntryData])
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let nested = try? container.decode([String: EntryData].self)
        let array = try? container.decode([EntryData].self)
        let string = try? container.decode(String.self)
        switch (string, array, nested) {
        case (let s?, nil, nil):
            self = .string(s)
        case (nil, let a?, nil):
            self = .array(a)
        case (nil, nil, let n?):
            self = .nested(n)
        default:
            throw DecodingError.valueNotFound(
                EntryData.self, 
                .init(codingPath: decoder.codingPath, 
                      debugDescription: "Value must be either string, array or a dictionary!", 
                      underlyingError: nil))
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let s):
            try container.encode(s)
        case .array(let a):
            try container.encode(a)
        case .nested(let n):
            try container.encode(n)
        }
    }
}

现在您可以:

let entry = try JSONDecoder().decode(Entry.self, from: data)