Swift Mirrors and JSON

Generating JSON dictionaries with reflection

Inspired by Mike Ash’s tweet, I tried generating JSON dictionaries in Swift by using the new reflection features. First of all, let’s create two structs for holding a person and an address:

struct Address {
    var street: String
}

struct Person {
    var name: String = "John"
    var age: Int = 42
    var dutch: Bool = false
    var address: Address? = Address(street: "Market St.")
}

let john = Person()

We can introspect values using the reflect function. It returns a MirrorType, which is a value we can use to inspect the type:

reflect(john)

Every MirrorType value has a property count, which counts the number of children (e.g. the address struct has a single child, street, whereas the person struct has four children, one for every field). We can extend MirrorType with a simple children property that loops over the children and returns them in an array:

extension MirrorType {
    var children: [(String,MirrorType)] {
        var result: [(String, MirrorType)] = []
        for i in 0..<self.count {
            result.append(self[i])
        }
        return result
    }
}

We will implement our solution in two steps. First, we’ll create a protocol JSON which converts any value into a JSON-serializable object. Because serialization might fail, we’ll mark it as throws:

protocol JSON {
    func toJSON() throws -> AnyObject?
}

Now comes the big trick I copied from Mike. We can provide a default implementation by extending the protocol. This default implementation reflects a value, and loops over the children, recursively serializing them as well. If the type doesn’t have any children, we assume it’s a primitive (e.g. a String or an Int) and don’t serialize it.

extension JSON {
    func toJSON() throws -> AnyObject? {
        let mirror = reflect(self)
        if mirror.count > 0  {
            var result: [String:AnyObject] = [:]
            for (key, child) in mirror.children {
                if let value = child.value as? JSON {
                  result[key] = try value.toJSON()
                } else {
                    throw CouldNotSerializeError.NoImplementation(source: self, type: child)
                }
            }
            return result
        }
        return self as? AnyObject
    }
}

Because of the implementation above, we can now easily derive JSON generation for structs and simple values, without having to implement it:

extension Address: JSON { }
extension Person: JSON { }
extension String: JSON { }
extension Int: JSON { }
extension Bool: JSON { }

For optional values, we want to return nil in case the value isn’t there, and otherwise try to serialize it. This is how you can override the toJSON function for a more specific type:

extension Optional: JSON {
    func toJSON() throws -> AnyObject? {
        if let x = self {
            if let value = x as? JSON {
                return try value.toJSON()
            }
            throw CouldNotSerializeError.NoImplementation(source: x, type: reflect(x))
        }
        return nil
    }
}

Finally, to test that it worked:

do {
    try john.toJSON()
} catch {
    print(error)
}

The full code is in a gist. It runs fine in a real app, but seems to not always work in a playground. After posting the gist on Twitter yesterday, Rich pointed out that I was late to the party: Matthew Cheok already implemented JSONCodable, which is a library that’s ready for use!