具有递归枚举的Swift Codable协议

时间:2017-11-06 14:58:40

标签: swift cocoa cocoa-touch swift4 codable

假设我有一个类似下面的模型,它允许我构建一个Foo对象树。

struct Foo {

    var kind : Kind

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

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

3 个答案:

答案 0 :(得分:5)

根据@PauloMattos的回答,这里是最终的结构:

Base Foo struct:

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
    }
}

可编码协议扩展:

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 Protocol扩展(从树中输出字符串):

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))

答案 1 :(得分:4)

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应遵循类似的逻辑;)

答案 2 :(得分:0)

Decoadable welcome协议的

Here is a great post及其用法。

我认为在 Enum 部分的帖子底部,您可以找到所需内容,但如果您不想阅读文章here is the gist,那么可以有帮助的。