How to loop over struct properties in Swift?
Although old question, with Swift evolving this question has new answer. I think that you approach is way better for the described situation, however original question was how to iterate over struct properties, so here is my answer(works both for classes and structs)
You can use Mirror Structure Reference. The point is that after calling reflect
to some object you get it's "mirror" which is pretty sparingly however still useful reflection.
So we could easily declare following protocol, where key
is the name of the property and value
is the actual value:
protocol PropertyLoopable
{
func allProperties() throws -> [String: Any]
}
Of course we should make use of new protocol extensions to provide default implementation for this protocol:
extension PropertyLoopable
{
func allProperties() throws -> [String: Any] {
var result: [String: Any] = [:]
let mirror = Mirror(reflecting: self)
guard let style = mirror.displayStyle where style == .Struct || style == .Class else {
//throw some error
throw NSError(domain: "hris.to", code: 777, userInfo: nil)
}
for (labelMaybe, valueMaybe) in mirror.children {
guard let label = labelMaybe else {
continue
}
result[label] = valueMaybe
}
return result
}
}
So now we can loop over the properties of any class
or struct
with this method. We just have to mark the class as PropertyLoopable
.
In order to keep things static(as in the example) I will add also a singleton:
struct ReuseID: PropertyLoopable {
static let instance: ReuseID = ReuseID()
}
Whether singleton used or not, we could finally loop over the properties like follows:
do {
print(try ReuseID.instance.allProperties())
} catch _ {
}
And that's it with looping struct properties. Enjoy swift ;)
Using hris.to's awesome answer, I wanted to provide a Swift 3 answer that's more to the point and doesn't use singletons.
Protocol & Extension:
protocol Loopable {
func allProperties() throws -> [String: Any]
}
extension Loopable {
func allProperties() throws -> [String: Any] {
var result: [String: Any] = [:]
let mirror = Mirror(reflecting: self)
// Optional check to make sure we're iterating over a struct or class
guard let style = mirror.displayStyle, style == .struct || style == .class else {
throw NSError()
}
for (property, value) in mirror.children {
guard let property = property else {
continue
}
result[property] = value
}
return result
}
}
Example:
struct Person: Loopable {
var name: String
var age: Int
}
var bob = Person(name: "bob", age: 20)
print(try bob.allProperties())
// prints: ["name": "bob", "age": 20]
Now there's a much easier way to do this:
1: Create an Encodable protocol extension:
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
2: Make your struct/class conform to Encodable protocol
struct MyStruct: Encodable {...}
class MyClass: Encodable {...}
And then you can get a Dictionary representing your struct/class instance at any time:
var a: MyStruct
var b: MyClass
print(a.dictionary)
print(b.dictionary)
And then you can loop through the keys:
for (key, value) in a.dictionary { ... }
for (key, value) in b.dictionary { ... }