1
votes

I am attempting to make a tree data structure obey the encodable protocol. The tree terminates in "some object" that obeys the protocol "Terminal". Terminal extends Codable.

Each node of the tree is a pair. It has a key, and a value. The value is either a pair, or a Terminal.

There are two main issues:

1) I would like this structure to encode to JSON such that

class Pair: Codable {
   var key: String?
   var value: Codable?
}

let outputSimple = Pair(key: "a test key", value: "a test value")
// encodes to
// {"a test key": "a test value"}
// whereas currently encodes to
// {}

let outputComplex = Pair(key: "a parent", value: Pair(key: "another pair", value: "a test value"))
// encodes to
// {"a parent": {"another pair", "a test value"}}

EDIT: part 2 might be confusing the issue slightly. To clarify issue above, if I had

class Pair: Codable {
   var key: String
   var value: String
}
let p = Pair(key:"foo", value: "bar")

how could I get it to output {"foo":"bar"} rather than {key:foo, value:bar}? I tried

    override func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        container.encode(contentsOf: [key: value])
    }

but get the error "Instance method 'encode(contentsOf:)' requires that '(key: _, value: _)' conform to 'Encodable'"

2) I was attempting the following but it doesn't appear to work. I get "Pair does not conform to protocol 'Decodable'"

protocol TreeNode: Codable {} 
struct Pair: TreeNode {
   var key: String?
   var value: TreeNode?
}

extension String: TreeNode {}

This one I can get around by making TreeNode a class with Pair a subclass. It's also likely this is correct Swift behaviour. However, I wondered if more pairs of eyes could explain the issue. I assumed that as long as I ensured all values are either of type pair, or something else that obeys Codable then it would work.

1
Have a look at GenericJSON for creating a dynamic, type-safe JSON using Codable. You could simplify that to fit your need for a Tree/Pair. - Dávid Pásztor
Thanks. That's the approach I'm taking but it involves wrapping the terminal nodes in a class. The encode method for that class then does the GenericJSON style encoding - Nick Jones

1 Answers

3
votes

This cannot work.

value must be a concrete type which conforms to Codable, not the protocol itself or a second protocol conforming to Codable

What you can do is to declare value as generic

class Pair<T: Codable>: Codable {
    var key: String?
    var value: T?

    init(key: String?, value: T?) {
        self.key = key
        self.value = value
    }
}

It can encode both outputSimple and outputComplex