Let's say that I have a model like the following, which allows me to build a tree of Foo objects.
struct Foo {
var kind : Kind
enum Kind {
case node([Foo])
case leaf
}
}
How can I make this Codable, specifically for the case node([Foo])
?
Here's the final struct, based on the answer from @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
}
}
Codable Protocol extension:
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 (to output string from the tree):
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
}
}
And example testing code:
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)
}
Output:
Foo to JSON:
{
"name" : "ROOT",
"nodes" : [
{
"name" : "A"
},
{
"name" : "D",
"nodes" : [
{
"name" : "B"
},
{
"name" : "C"
}
]
}
]
}
JSON to Foo:
ROOT: (A, D: (B, C))
One possible encoding for the Foo
recursive data type could be:
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.
}
}
}
A simple test using the JSON encoder:
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)
would then output the following JSON:
{
"name" : "ROOT",
"nodes" : [
{
"name" : "A"
},
{
"name" : "C"
},
{
"name" : "B"
}
]
}
Conforming to Decodable
should follow a similar logic ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With