I found that RPatel99's answer worked the best for me, but it did not handle the case where there as an array of Loopables inside another Loopable.
Here is a version that fixes that issue.
protocol Loopable {
func allProperties(limit: Int) -> [String: Any]
}
extension Loopable {
func allProperties(limit: Int = Int.max) -> [String: Any] {
return props(obj: self, count: 0, limit: limit)
}
private func props(obj: Any, count: Int, limit: Int) -> [String: Any] {
let mirror = Mirror(reflecting: obj)
var result: [String: Any] = [:]
for (property, value) in mirror.children {
var val = value
if let values = value as? [Loopable] {
var vals = [Any]()
for val in values {
vals.append(val.allProperties())
}
val = vals
}
guard let prop = property else { continue }
if limit == count {
result[prop] = val
} else {
let subResult = props(obj: val, count: count + 1, limit: limit)
result[prop] = subResult.count == 0 ? val : subResult
}
}
return result
}
}
class C {
var w = 14
}
class B: Loopable {
var x = 12
var y = "BHello"
var z = C()
static func test() -> String {
return "Test"
}
}
class A: Loopable {
var a = 1
var c = 10.0
var d = "AHello"
var e = B()
var f = [B(), B(), B()]
var g = [1,2,3,4]
var h: [String: Any] = ["A": 0, "B": B().allProperties()]
var i: Int?
}
print(A().allProperties())
The result I get is this
["h": ["A": 0, "B": ["z": ["w": 14], "y": "BHello", "x": 12]], "e": ["y": "BHello", "z": ["w": 14], "x": 12], "f": [["y": "BHello", "z": ["w": 14], "x": 12], ["x": 12, "z": ["w": 14], "y": "BHello"], ["y": "BHello", "x": 12, "z": ["w": 14]]], "i": nil, "g": [1, 2, 3, 4], "a": 1, "d": "AHello", "c": 10.0]
I hope someone else will find this useful.
This result could probably also be achieved by something like this