Swift Node Tree to complex Json

Viewed 87

I'm working on a tool written with swift that needs to export a tree to json. I've been following Swift tree approach described here as a guide.

I create a tree using the following code

class Node {
  var value: String
  var children: [Node] = []
  weak var parent: Node?

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

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

let beverages = Node(value: "beverages")

let hotBeverages = Node(value: "hot")
let coffee = Node(value: "coffee")

let coldBeverages = Node(value: "cold")
let water = Node(value: "water")
let soda = Node(value: "soda")

beverages.add(child: hotBeverages)
hotBeverages.add(child: coffee)

beverages.add(child: coldBeverages)
coldBeverages.add(child: water)
coldBeverages.add(child: soda)

Now I need to export this code to json. I'm passing it into webkit so I can load a hierarchal tree via D3. This means I need the export it so it matches the following format...

[
 {
  "name": "beverages",
  "children": [
    {
     "name": "cold", 
     "children": [
      {"name": "water"}, 
      {"name": "soda"}
    ]},
    {
      "name": "warm", 
      "children": [
        {"name": "coffee"}
      ]
    }]
  }
]

I've seen a lot of posts about exporting swift to basic json, but can't quite wrap my head around doing it with a tree like this. Any help would be appreciated!

Note: I don't need pretty printed json. That's just for readability here

1 Answers

You simply need to make Node conform to Encodable. The only tricky step you need to do is to only encode children in case it has any elements, otherwise only encode value. You also need to declare a CodingKey conformant enum to tell Encodable to encode the value property using the JSON key name rather than value.

extension Node: Encodable {
    private enum CodingKeys: String, CodingKey {
        case value = "name"
        case children
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(value, forKey: .value)
        if !children.isEmpty {
            try container.encode(children,forKey: .children)
        }
    }
}

And then use it like this

do {
    let encodedBevarages = try JSONEncoder().encode(beverages)
    print(String(data: encodedBevarages, encoding: .utf8) ?? "Encoding failed")
} catch {
    error
}

Output:

{"name":"beverages","children":[{"name":"hot","children":[{"name":"coffee"}]},{"name":"cold","children":[{"name":"water"},{"name":"soda"}]}]}

Related