How to compare two array of objects? - ios

I have a class A:
class A {
var identifier: String?
var quantity: Int = 0
}
Two arrays of A instances:
var array1: [A] = [a1, a2, a3, a4]
var array2: [A] = [a5, a6, a7, a8]
I don't know which is the best way to check:
array1==array2 if a1.identifier == a5.identifier, a2.identifier == a6.identifier, a3.identifier==a7.identifier, a4.identifier==a8.identifier in Swift.
Please help me...

You can try like this:
let result = zip(array1, array2).enumerated().filter() {
$1.0 == $1.1
}.map{$0.0}

Swift 4
The following method makes it much more easy.
Method 1 - Using Equatable Protocol
Step1 - Make your class 'A' equatable as follows
extension A: Equatable {
static func ==(lhs: A, rhs: A) -> Bool {
// Using "identifier" property for comparison
return lhs.identifier == rhs.identifier
}
}
Step2 - Sort your arrays in ascending or descending order
let lhsArray = array1.sorted(by: { $0.identifier < $1.identifier })
let rhsArray = array2.sorted(by: { $0.identifier < $1.identifier })
Step3 - Use == or elementsEqual comparison
let isEqual = lhsArray == rhsArray
OR
let isEqual = lhsArray.elementsEqual(rhsArray, by: { $0 == $1} )
Method 2 (Without Equatable Protocol)
Step 1 - Sort Array as described in Method1, step 2
Step 2 - Use elementsEqual
lhsArray.elementsEqual(rhsArray, by: { $0.identifier == $1.identifier })
Read more about Array Comparison here

Assume your data like that:
struct Person
{
let name: String
let id: Int
}
var people1 = [
Person(name: "Quang Hà", id: 42),
Person(name: "Lý Hải", id: 23),
Person(name: "Maria", id: 99)
]
var people2 = [
Person(name: "Maria yyy", id: 99),
Person(name: "Billy", id: 42),
Person(name: "David", id: 23)
]
This is the method to compare two arrays of people with id:
func areArrayPeopleEqual(people1:[Person], people2: [Person]) -> Bool {
var array1 = people1
var array2 = people2
// Don't equal size => false
if array1.count != array2.count {
return false
}
// sort two arrays
array1.sortInPlace() { $0.id > $1.id }
array2.sortInPlace() {$0.id > $1.id }
// get count of the matched items
let result = zip(array1, array2).enumerate().filter() {
$1.0.id == $1.1.id
}.count
if result == array1.count {
return true
}
return false
}

This method could be used if you have some parameter to compare:
let difference = currentObjects
.filter({ currentObject in
!(newObjects
.contains(where: { $0.identifier == currentObject.identifier }))
})

I found this really easy solution at https://www.hackingwithswift.com/example-code/language/how-to-find-the-difference-between-two-arrays
extension Array where Element: Hashable {
func difference(from other: [Element]) -> [Element] {
let thisSet = Set(self)
let otherSet = Set(other)
return Array(thisSet.symmetricDifference(otherSet))
}
}
let names1 = ["a1", "A4", "a3", "a4"]//["John", "Paul", "Ringo"]
let names2 = ["a1", "a5", "a4","a1.1"]//["Ringo", "George"]
let difference = names1.difference(from: names2)

First we extend Equatable class, to have a DRY code, than if the 2 arrays are always of the same size, or if at least the first one is <= than the second you can go with this solution.
Pay attention that you are working with optionals, you may have to unwrap them before.
class A {
var identifier: String?
var quantity: Int = 0
init(identifier: String, quantity: Int) {
self.identifier = identifier
self.quantity = quantity
}
}
let a1: A = A(identifier: "1", quantity: 1)
let a2: A = A(identifier: "2", quantity: 2)
let a3: A = A(identifier: "3", quantity: 3)
let a4: A = A(identifier: "4", quantity: 4)
let a5: A = A(identifier: "1", quantity: 1)
let a6: A = A(identifier: "2", quantity: 2)
let a7: A = A(identifier: "3", quantity: 3)
let a8: A = A(identifier: "4", quantity: 4)
var array1: [A] = [a1, a2, a3, a4]
var array2: [A] = [a5, a6, a7, a8]
func areEquals(array1: [A], array2: [A]) -> Bool {
if array1.count < array2.count {
return false
}
for i in 0...array2.count - 1 {
if array1[i] != array2[i] {
return false
}
}
return true
}
extension A: Equatable {
static func ==(lhs: A, rhs: A) -> Bool {
//you can choose how and when they should be equals
return lhs.identifier == rhs.identifier
}
}

try this code, let me know if it works
func toDictionary<E, K, V>(
array: [E],
transformer: (element: E) -> (key: K, value: V)?)
-> Dictionary<K, V>
{
return array.reduce([:]) {
(var dict, e) in
if let (key, value) = transformer(element: e)
{
dict[key] = value
}
return dict
}
}
then you can execute a check like below
let areEqual = array1.count == array2.count;
if areEqual {
let dict1 = toDictionary(array1) { ($0.identifier, $0.quantity) }
let dict2 = toDictionary(array2) { ($0.identifier, $0.quantity) }
areEqual = NSDictionary(dictionary: dict1).isEqualToDictionary(dict2)
}
print(areEqual)
disclaimer: function toDictionary has been took form here

Related

Swift iOS -Compare elements in 1 array by property

I have an array of objects and I want to compare the objects based on property to find out if the properties are all the same. Right now I loop through all the objects, place all values of the properties in a separate array, and then use filterArr.allSatisfy { $0 == filterArr.last } to detemermine wether the properties are all the same or not.
This method works but I know there has to be a more elegant way then what I'm doing.
I actually went looking for an answer to this but every single thing I came across was comparing the elements of 2 different arrays instead of 1.
class IceCream {
var flavor: String?
var price: Double?
}
let iceCream1 = IceCream()
iceCream1.flavor = "vanilla"
iceCream1.price = 1.5
let iceCream2 = IceCream()
iceCream2.flavor = "chocolate"
iceCream2.price = 2.0
let iceCream3 = IceCream()
iceCream3.flavor = "vanilla"
iceCream3.price = 1.5
let iceCream4 = IceCream()
iceCream4.flavor = "strawberry"
iceCream4.price = 2.5
let iceCreams = [iceCream1, iceCream2, iceCream3, iceCream4]
var filterArr = [String]()
for iceCream in iceCreams {
filterArr.append(iceCream.flavor ?? "")
}
let areItemsEqual = filterArr.allSatisfy { $0 == filterArr.last }
print(areItemsEqual) // prints false
You can avoid having to initialize and then assign the properties on separate lines with a struct.
struct IceCream {
let flavor: String?
let price: Double?
}
let iceCreams: [IceCream] = [
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "chocolate", price: 2.0),
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "strawberry", price: 2.5)
]
Using the generics sugar provided by #Alexander and #matt, we have a nice looking extension.
extension Collection {
func allEqual<T: Equatable>(by key: KeyPath<Element, T>) -> Bool {
return allSatisfy { first?[keyPath:key] == $0[keyPath:key] }
}
}
print(iceCreams.allEqual(by: \.flavor))
Alternatively, you could specify an IceCream be equal to one another by flavor.
extension IceCream: Equatable {
static func == (lhs: IceCream, rhs: IceCream) -> Bool {
return lhs.flavor == rhs.flavor
}
}
extension Collection where Element: Equatable {
func allEqual() -> Bool {
return allSatisfy { first == $0 }
}
}
print(iceCreams.allEqual())
Here's a pretty Swifty way to do it. I define an extension on Collection that checks for equality among the collection's items, according to a given predicate:
extension Collection {
func allEqual<T: Equatable>(by deriveKey: (Element) -> T) -> Bool {
guard let firstElement = self.first else {
return true // empty lists are all-equal
}
let sampleKey = deriveKey(firstElement)
return self.dropFirst().allSatisfy{ deriveKey($0) == sampleKey }
}
}
struct IceCream {
let flavor: String
let price: Double
}
let iceCreams = [
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "chocolate", price: 2.0),
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "strawberry", price: 2.5)
]
let allItemsEqualByFlavour = iceCreams.allEqual(by: { $0.flavor})
print(allItemsEqualByFlavour) // false
let vanillaOnlyIceCreams = iceCreams.filter{ $0.flavor == "vanilla" }
print(vanillaOnlyIceCreams.allEqual(by: { $0.flavor})) // true
Here's an elegant way to make sure your ice creams are the same along any arbitrary axis, i.e. either flavor or price or any other equatable property you may later inject:
extension Array {
func allSameForProperty<T:Equatable> (_ p:KeyPath<Element,T>) -> Bool {
return self.isEmpty || self.allSatisfy{
self.first![keyPath:p] == $0[keyPath:p]
}
}
}
Let's test it. First, some initial conditions:
struct Icecream {
let flavor : String
let price : Double
}
let arr = [
Icecream(flavor: "vanilla", price: 1.5),
Icecream(flavor: "vanilla", price: 1.75)
]
And now the actual test:
arr.allSameForProperty(\Icecream.flavor) // true
arr.allSameForProperty(\Icecream.price) // false

Remove objects that have duplicate keys

My code...
class Inbox {
var user = "name"
var pmsg = "label"
var match = ""
var resim = "photo"
var userID = ""
var distance = ""
var updated = ""
var isAttendingNow = ""
var isAttendingNowText = ""
init(user : String, pmsg: String, match: String, resim: String, userID : String, distance: String, updated: String, isAttendingNow: String, isAttendingNowText: String) {
self.user = user
self.pmsg = pmsg
self.match = match
self.resim = resim
self.userID = userID
self.distance = distance
self.updated = updated
self.isAttendingNow = isAttendingNow
self.isAttendingNowText = isAttendingNowText
}
}
var arrayOfRels: [Inbox] = [Inbox]()
My goal is to remove duplicate items for userID key.
How can I achieve that?
You could use a set to figure out which useIDs are unique:
func filteredRels(source [Inbox]) -> [Inbox] {
var keys: Set<String> = []
return source.filter {
if keys.contains($0.userID) {
return false
} else {
keys.insert($0.userID)
return true
}
}
}
(Banged out in the editor, so it might need some minor cleanup.)
Use Hashable
class RRR : Hashable {
var hashValue: Int = 0
static func == (lhs: RRR, rhs: RRR) -> Bool {
// in your case set only userID
return lhs.name == rhs.name && lhs.age == rhs.age
}
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
//
let arr = [RRR(name: "qqq", age: 12) ,RRR(name: "qqq", age: 12) , RRR(name: "hhhh", age: 12) , RRR(name: "ppppp", age: 12) ]
let set = Array(Set(arr))
print(set) // [ RRR(name: "qqq", age: 12) , RRR(name: "hhhh", age: 12) , RRR(name: "ppppp", age: 12)]
Checkout this:
extension Sequence where Iterator.Element: Hashable {
func uniqueOrdered() -> [Iterator.Element] {
return reduce([Iterator.Element]()) { $0.contains($1) ? $0 : $0 + [$1] }
}
}
class Inbox: Hashable {
...
...
static func == (lhs: User, rhs: User) -> Bool {
return lhs.userID == rhs.userID
}
}
arrayOfRels.uniqueOrdered()
You could do this in a couple of lines using a set:
var unique = Set<String>()
arrayOfRels = arrayOfRels.filter{unique.insert($0.userID).inserted}

iOS Swift 3: Comparing One all elements of Array in another array

I have struct model like
struct ModelA {
let text: String
let id: Int
}
extension ModelA: Equatable {}
func ==(lhs: ModelA, rhs: ModelA) -> Bool {
let areEqual = lhs.id == rhs.id
return areEqual
}
i have created arrays of this model
let a1:[ModelA] = [ModelA(text: "10", id: 11), ModelA(text: "11", id: 12)]
let a2:[ModelA] = [ModelA(text: "11", id: 12)]
and having a comparing function
func isEqualArray(first array1: [Any], second array2: [Any]) -> Bool {
let set1 = NSSet(array: array1)
let set2 = NSSet(array: array2)
return set1.isSubset(of: set2 as! Set<AnyHashable>)
}
so when i'm trying to cross check
let flag = isEqualArray(first: a1, second: a2)
print("### \(flag)")
It crashes on function return
What am I doing wrong?
Your struct needs to conform to both equatable and hashable in order to be used in a Set. It seems that you only care about the id, so a simple implementation would be:
struct ModelA {
let text: String
let id: Int
}
extension ModelA: Equatable {
static func ==(lhs: ModelA, rhs: ModelA) -> Bool {
return lhs.id == rhs.id
}
}
extension ModelA: Hashable {
var hashValue: Int {
return id
}
}
Now, you can use Swift sets in your isEqualArray function; you also need to consider which set is smaller since you are using isSubSet(of:):
func isEqualArray(first array1: [AnyHashable], second array2: [AnyHashable]) -> Bool {
let set1: Set<AnyHashable>
let set2: Set<AnyHashable>
if array1.count > array2.count {
set1 = Set(array1)
set2 = Set(array2)
} else {
set1 = Set(array2)
set2 = Set(array1)
}
return set2.isSubset(of: set1)
}
Your code actually determines if one array is a subset of another, not if the arrays are equal, so I am not sure if that is what you want.

How to sort NSMutableArray that contains object of type Modal?

I’ve an NSMutableArray (say accountListArray). The content of array is Model (say BankAccountModel with property defaultAcc - Y/N).
Now I want to sort the content of accountListArray based on default property i.e. all account that is defaultAcc enabled (Y) should be on top.
class BankAccountModel: NSObject {
static let sharedInstance: BankAccountModel = BankAccountModel()
var defaultAcc = "" // Y or N
}
How to do it?
Here is my example. It will sort array and after sort person with name 2 and person with name 4 will be in top of the array
class People{
var name = ""
var isMale:Bool = false
init(name:String,isMale:Bool){
self.name = name
self.isMale = isMale
}
}
var person1 = People(name: "1",isMale: false)
var person2 = People(name: "2",isMale: true)
var person3 = People(name: "3",isMale: false)
var person4 = People(name: "4",isMale: true)
var array = [person1,person2,person3,person4]
array.sort() { Int(NSNumber(value:($0.isMale))) > Int(NSNumber(value:($1.isMale))) }
Additional to the answers above. You can define your model as Comparable, then implement your own >, <, == protocol:
For example:
class BankAccountModel: Comparable { //or struct
var accountHolder: String = ""
var balance: Decimal?
var enabled: Bool = false
init(holder: String, balance: Decimal, enabled: Bool) {
self.accountHolder = holder
self.balance = balance
self.enabled = enabled
}
static func == (lhs: BankAccountModel, rhs: BankAccountModel) -> Bool {
return lhs.enabled == rhs.enabled
}
static func > (lhs: BankAccountModel, rhs: BankAccountModel) -> Bool {
return lhs.enabled || (!lhs.enabled && !rhs.enabled)
}
static func < (lhs: BankAccountModel, rhs: BankAccountModel) -> Bool {
return rhs.enabled || (!rhs.enabled && !lhs.enabled)
}
}
let account1 = BankAccountModel(holder: "Holder 1", balance: Decimal(10.0), enabled: true)
let account2 = BankAccountModel(holder: "Holder 2", balance: Decimal(20.3), enabled: false)
let account3 = BankAccountModel(holder: "Holder 3", balance: Decimal(-1.0), enabled: false)
let account4 = BankAccountModel(holder: "Holder 4", balance: Decimal(0.0), enabled: true)
var allAccounts = [account1, account2, account3, account4]
allAccounts.sort(by: {return $0 > $1})
Try this :
Create Model property as below
class BankAccountModel: NSObject {
static let sharedInstance: BankAccountModel = BankAccountModel()
var isDefault: Bool = false
// assign value
if let objDefault = dictionary["default"] {
isDefault = (objDefault as! String) == "Y" ? true : false
}
// var default = "" // Y or N
}
Sort as below
self.arrOfModels.sort {
return $0.isDefault && !$1.isDefault // isSelected is your flag . you need to change with your flag
}
OR
Another Solution with string
self.arrModels.sort {
return ($0. defaultAcc == "Y") && ($1. defaultAcc == "N")
}
Swift 3.0
Hope will work for you.
let sortedArr = accountListArray.sorted(by: {
let item1 = $0 as! BankAccountModel
let item2 = $1 as! BankAccountModel
if item1.defaultAcc > item2.defaultAcc {
return true
}
return false
})

How to loop over struct properties in Swift?

Is it possible to iterate over properties of a struct in Swift?
I need to register cells-reuse identifiers in a view controller that makes use of many different cell types (cells are organized in different nib files). So my idea was to put all reuse identifiers and the corresponding nib-files as static tuple-properties (reuseID, nibName) in a struct. But how can I iterate over all of them to register the cells with the tableView?
I already tried something (see my answer below). But is there a more easy way to do this, e.g. without putting every property inside an array?
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 { ... }
I made a recursive function based on #John R Perry's solution that goes deeper into properties that are objects. It also takes an parameter to limit how many levels deep it goes (default is Int.max) to help prevent stackoverflow's:
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 (prop, val) in mirror.children {
guard let prop = prop 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
}
}
I got rid of the check for if the object is a class or struct because that the parameter not being a class or struct is the base case of the recursive function, and it was easier to handle it manually than with errors.
Testing it:
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 = true
var f = B()
var g = [1,2,3,4]
var h: [String: Any] = ["A": 0, "B": "Dictionary"]
var i: Int?
}
print(A().allProperties())
prints:
["e": true, "g": [1, 2, 3, 4], "f": ["z": ["w": 14], "x": 12, "y": "BHello"], "h": ["A": 0, "B": "Dictionary"], "c": 10.0, "i": nil, "d": "AHello", "a": 1]
(Dictionaries are unordered, so if you get a different order, that's why)
Here is an example of iterating over struct properties (reuse identifiers of UITableViewCells and the corresponding NIB-names) using Swifts tuple feature. This is useful if you like organizing your cells in nib files and have a UIViewController that makes use of many different cell types.
struct ReuseID {
static let prepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
static let threeTitledIconCell = "ThreeTitledIconCell"
static let usageCell = "UsageCell"
static let detailsCell = "DetailsCell"
static let phoneNumberCell = "PhoneNumberCell"
static let nibNamePrepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
static let nibNameThreeTitledIconCell = "IconCellWith3Titles"
static let nibNameUsageCell = "ListElementRingViewCell"
static let nibNameDetailsCell = "ListElementStandardViewCell"
static let nibNamePhoneNumberCell = "PhoneNumberCell"
static let allValuesAndNibNames = [
(ReuseID.prepaidRechargeCreditCell, ReuseID.nibNamePrepaidRechargeCreditCell),
(ReuseID.threeTitledIconCell, ReuseID.nibNameThreeTitledIconCell),
(ReuseID.usageCell, ReuseID.nibNameUsageCell),
(ReuseID.detailsCell, ReuseID.nibNameDetailsCell),
(ReuseID.phoneNumberCell, ReuseID.nibNamePhoneNumberCell)]
}
With that struct it is easy to register all cell types using a for-loop:
for (reuseID, nibName) in ReuseID.allValuesAndNibNames {
if let xibPath = NSBundle.mainBundle().pathForResource(nibName, ofType: "nib") {
let fileName = xibPath.lastPathComponent.stringByDeletingPathExtension
self.tableView.registerNib(UINib(nibName: fileName, bundle: nil), forCellReuseIdentifier: reuseID)
} else {
assertionFailure("Didn't find prepaidRechargeCreditCell 👎")
}
}
Knowing that in Swift 1.2 you could use reflect(), and since Swift 2 you can use Mirror, here is an addition to hris.to's answer for Swift 3 and 4.
protocol Loopable {
var allProperties: [String: Any] { get }
}
extension Loopable {
var allProperties: [String: Any] {
var result = [String: Any]()
Mirror(reflecting: self).children.forEach { child in
if let property = child.label {
result[property] = child.value
}
}
return result
}
}
Usage on any struct or class:
extension NSString: Loopable {}
print("hello".allProperties)
// ["_core": Swift._StringCore(_baseAddress: Optional(0x00000001157ee000), _countAndFlags: 5, _owner: nil)]
struct IdentifiersModel : Codable {
let homeReuseID: String = "Home_Reuse_ID"
let offersReuseID: String = "Offers_Reuse_ID"
let testReuseID: String = "Test_Reuse_ID"
func toDic() -> [String:Any] {
var dict = [String:Any]()
let otherSelf = Mirror(reflecting: self)
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value
}
}
return dict
}
}
Create a new instance from the struct and call toDic() function
let identifiersModel = IdentifiersModel()
print(identifiersModel.toDic())
Output:
["offersReuseID": "Offers_Reuse_ID", "testReuseID": "Test_Reuse_ID", "homeReuseID": "Home_Reuse_ID"]
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

Resources