CodableFirebase icon indicating copy to clipboard operation
CodableFirebase copied to clipboard

How to convert dictionary of object_id : object to array of objects?

Open pzmudzinski opened this issue 6 years ago • 4 comments

Let's say we have structure like this:

     {
        "id1": {
            "name":"name1",
            "connections": {
                "id1": 1,
                "id2": 1,
            }
        },
        "id2": {
            "name":"name2",
            "connections": {
                "id1": 1,
                "id2": 0,
            }
        }
    }

I want to map it to structure like this:

struct MyData {
 let name: String
 let id: String # "id1", "id2" ...
 let connections: [String]
}

As you see id is on upper level while name and connections are on lower level. Should I manually convert that dictionary to array before every parsing? Can I somehow setup "pre-parser" which in case of converting Dictionary to Array[MyData] will flatten from Dictionary to Array[Dictionary]?

Also as side question.. since snapshot returns Dictionary[String: Any] it seems you can decode it using regular default JSONDecoder (or can't you?) - what's the advantage of using this library? Probably I am missing a point here :)

pzmudzinski avatar Jun 13 '18 10:06 pzmudzinski

Here is an example for Cloud Firestore:

Fetch Collection of "MyData" I consider your id1, id2 etc. are documents inside a collection. You can fetch all documents and map them into an array of MyData like this:

reference.getDocuments { (querySnapshot, error) in
            if let error = error {
                print(error.localizedDescription)
            } else {
                if let documents = querySnapshot?.documents {
                    let myDataArray = documents.map({ (snapshot) -> MyData in
                        let myData = try! FirestoreDecoder().decode(MyData.self, from: snapshot.data())
                        return myData
                    })
                    completion(myDataArray) // This is your array of MyData objects
                }
            }
        }

Another Example Here is a code example for storing an array of MyData inside a collection.

       let batch = Firestore.firestore().batch()
        
        for myData in arrayOfMyData {
            let reference = db.collection("myData").document()
            let myDataDictionary = try! FirestoreEncoder().encode(myData)
            batch.setData(myDataDictionary, forDocument: reference)
        }
        
        batch.commit { (error) in
            if let error = error {
                print(error.localizedDescription)
            } else {
                completion("Upload successful")
            }
        }

There are two problems in your code:

1. Your myData.id is not part of the JSON Object itself. I would put the documentID into the JSON object like this:

{
        "id1": {
            "documentID: String #id1"
            "name":"name1",
            "connections": {
                "id1": 1,
                "id2": 1,
            }
        },
        "id2": {
            "documentID: String #id2"
            "name":"name2",
            "connections": {
                "id1": 1,
                "id2": 0,
            }
        }
    }

This makes it easier for FirestoreDecoder to map the documentID to the MyData struct.

2. I would consider putting the connections inside subcollections. If you want to add id3 or id1000 to document1, you have to fetch document1 with all the connections inside an array and add id1000 to it. This can cause problems in scalability the more connections you have inside one document.

Why CodableFirestore / CodableFirebase

Consider you have a struct like this

struct Person: Codable {
    var name: Name? // Firstname, Lastname, Salutation
    var address: Address? // Street, PostalCode, Country etc.
    var contactDetails: ContactDetails? // Phone, Mobile, etc.
    var foodPreferences: FoodPreferences? // ...
    ....
    ....
    
}

To encode for firebase / firestore or decode from it, you have to write a lot of boilerplate code. With CodableFirestore / CodableFirebase you can avoid encoding / decoding boilerplate code. That is why I am using it.

luca251117 avatar Jun 13 '18 10:06 luca251117

Sorry, I should make it clear - I am using Firebase Realtime Database, not Firestore.

pzmudzinski avatar Jun 13 '18 11:06 pzmudzinski

Ok someone else can get this up for you, I never used FRD. Regardless of the data structure, encoding and decoding with CodableFirebase works identical for FRD, so maybe my post can help you to implement it for FRD.

luca251117 avatar Jun 13 '18 11:06 luca251117

For now I've just created method for converting dictionary of keys:objects to array of objects:

extension Dictionary {
    func toArrayOfDictionaries() -> [Dictionary<String, Any>] {
        return self.map {
            let key = $0
            var value = $1 as! Dictionary<String, Any>
            
            value["id"] = key
            return value
            }.filter { $0["id"] != nil }
    }
}

Then use it like that:

{ [weak self] snapshot in
                guard let value = snapshot.value as? [String: Any] else {
                    return []
                }
                
            let neighborhoods = value.toArrayOfDictionaries()
            return try self?.decoder.decode([Neighborhood].self, from: neighborhoods) ?? []
        }

pzmudzinski avatar Jun 15 '18 05:06 pzmudzinski