[SOLVED] Swift Tree Structure, encoding and accessing nodes

Issue

I am having trouble figuring out why the following does not run. The goal is to create a tree structure with reference types that can be saved to a json file, but the following playground code does not run.

The root node has a nil parent, but I thought the encoder ignored nil values. In my app I get an EXC_BAD_ACCESS. Does this need to be done with structs instead of classes, and if so, is there a way to access a particular node without traversing the entire tree? Any help appreciated.

import Cocoa

final class Node: Codable {
    var id: UUID
    var data: [MyData]
    var children: [Node]
    var parent: Node? = nil
    
    init() {
        self.id = UUID()
        self.data = []
        self.children = []
    }
    
    func add(data: MyData) {
        data.parent = self
        self.data.append(data)
    }
    
    func add(child: Node) {
        child.parent = self
        self.children.append(child)
    }
}

final class MyData: Codable {
    var id: UUID
    var label: String
    var value: String
    var parent: Node? = nil
    
    init(label: String, value: String) {
        self.id = UUID()
        self.label = label
        self.value = value
    }
}

var root = Node()
root.add(data: MyData(label: "label 1", value: "value 1"))
root.add(data: MyData(label: "label 2", value: "value 2"))
var child = Node()
child.add(data: MyData(label: "label 3", value: "value 3"))
root.add(child: child)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try encoder.encode(root)
print(String(data: json, encoding: .utf8)!)

Solution

The problem is that by default Codable tries to encode/decode all properties of a conforming type. This means that the children try to encode/decode their parent, which parent contains the children as well, hence leading to an infinite loop.

You need to manually specify which properties to encode/decode by providing a CodingKey conformant type. By leaving out the parent property from both Node.CodingKeys and MyData.CodingKeys, you resolve the infinite loop.

import Foundation

final class Node: Codable {
    let id = UUID()
    var data: [MyData]
    var children: [Node]
    weak var parent: Node? = nil

    init() {
        self.data = []
        self.children = []
    }

    func add(data: MyData) {
        data.parent = self
        self.data.append(data)
    }

    func add(child: Node) {
        child.parent = self
        self.children.append(child)
    }

    private enum CodingKeys: String, CodingKey {
      case data
      case children
    }
}

final class MyData: Codable {
    let id = UUID()
    var label: String
    var value: String
    weak var parent: Node? = nil

    init(label: String, value: String) {
        self.label = label
        self.value = value
    }

    private enum CodingKeys: String, CodingKey {
      case label
      case value
    }
}

var root = Node()
root.add(data: MyData(label: "label 1", value: "value 1"))
root.add(data: MyData(label: "label 2", value: "value 2"))
var child = Node()
child.add(data: MyData(label: "label 3", value: "value 3"))
root.add(child: child)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try encoder.encode(root)
print(String(data: json, encoding: .utf8)!)

Answered By – Dávid Pásztor

Answer Checked By – Pedro (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.