Swift 具有递归枚举的 Codable 协议

Swift Codable protocol with recursive enums

假设我有一个如下所示的模型,它允许我构建 Foo 对象树。

struct Foo {

    var kind : Kind

    enum Kind {
        case node([Foo])
        case leaf
    }
}

如何制作这个 Codable,特别是 case node([Foo])

Foo 递归数据类型的一种可能 编码 可能是:

struct Foo: Encodable {
    var name: String // added a per-node payload as well.
    var kind: Kind

    enum Kind {
        case node([Foo])
        case leaf
    }

    enum CodingKeys: String, CodingKey {
        case name
        case nodes
    }

    func encode(to encoder: Encoder) throws {
        var dict = encoder.container(keyedBy: CodingKeys.self)
        try dict.encode(name, forKey: .name)
        switch kind {
        case .node(let nodes):
            var array = dict.nestedUnkeyedContainer(forKey: .nodes)
            try array.encode(contentsOf: nodes)
        case .leaf:
            break // Nothing to encode. 
        }
    }
}

使用 JSON 编码器的简单测试:

let a = Foo(name: "A", kind: .leaf)
let b = Foo(name: "C", kind: .leaf)
let c = Foo(name: "B", kind: .leaf)
let root = Foo(name: "ROOT", kind: .node([a, b, c]))

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try! encoder.encode(root)
let json = String(data: jsonData, encoding: .utf8)!
print(json)

然后会输出以下 JSON:

{
  "name" : "ROOT",
  "nodes" : [
    {
      "name" : "A"
    },
    {
      "name" : "C"
    },
    {
      "name" : "B"
    }
  ]
}

符合Decodable应该遵循类似的逻辑;)

Decoadable 协议的

Here is a great post 及其用法。

我认为在 Enum 部分的 post 底部你可以找到你需要的东西,但如果你不想阅读文章 here is the gist 这可能会有帮助。

这是最终结构,基于@PauloMattos 的回答:

基础 Foo 结构:

struct Foo {

    var name: String
    var kind: Kind

    enum Kind {
        case node([Foo])
        case leaf
    }

    init(name: String, kind: Kind) {
        self.name = name
        self.kind = kind
    }
}

Codable 协议扩展:

extension Foo : Codable {

    enum CodingKeys: String, CodingKey {
        case name
        case nodes
    }

    enum CodableError: Error {
        case decoding(String)
        case encoding(String)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        switch kind {
        case .node(let nodes):
            var array = container.nestedUnkeyedContainer(forKey: .nodes)
            try array.encode(contentsOf: nodes)
            break
        case .leaf:
            break
        }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // Assumes name exists for all objects
        if let name = try? container.decode(String.self, forKey: .name) {
            self.name = name
            self.kind = .leaf
            if let array = try? container.decode([Foo].self, forKey: .nodes) {
                self.kind = .node(array)
            }
            return
        }
        throw CodableError.decoding("Decoding Error")
    }
}

CustomStringConvertable 协议扩展(从树输出字符串):

extension Foo : CustomStringConvertible {

    var description: String {
        return stringDescription(self)
    }

    private func stringDescription(_ foo: Foo) -> String {
        var string = ""
        switch foo.kind {
        case .leaf:
            return foo.name
        case .node(let nodes):
            string += "\(foo.name): ("
            for i in nodes.indices {
                string += stringDescription(nodes[i])
                // Comma seperate all but the last
                if i < nodes.count - 1 { string += ", " }
            }
            string += ")"
        }
        return string
    }
}

以及示例测试代码:

let a = Foo(name: "A", kind: .leaf)
let b = Foo(name: "B", kind: .leaf)
let c = Foo(name: "C", kind: .leaf)
let d = Foo(name: "D", kind: .node([b, c]))
let root = Foo(name: "ROOT", kind: .node([a, d]))

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try! encoder.encode(root)
let json = String(data: jsonData, encoding: .utf8)!
print("Foo to JSON:")
print(json)

let decoder = JSONDecoder()
do {
    let foo = try decoder.decode(Foo.self, from: jsonData)
    print("JSON to Foo:")
    print(foo)
} catch {
    print(error)
}

输出:

Foo to JSON:
{
  "name" : "ROOT",
  "nodes" : [
    {
      "name" : "A"
    },
    {
      "name" : "D",
      "nodes" : [
        {
          "name" : "B"
        },
        {
          "name" : "C"
        }
      ]
    }
  ]
}
JSON to Foo:
ROOT: (A, D: (B, C))