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))
假设我有一个如下所示的模型,它允许我构建 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))