0

I am downloading a large file structure from Firebase. However when printing the keys and values one of the subfolders is showing as <__NSArrayM> I tried to cast it to [String:Any] but it is crashing. Here is my code:

DATABASE.mainOrder.child("incomplete").observe(DataEventType.value, with: { (snapshot) in
        let array = snapshot.value as! [String:[String:Any]]
        for (_, value) in array {
            for number in value {
                if "\(number.key)" == "itemsInOrder" {
                    let items = number.value as! [String:Any]
                    //let items = Array(arrayLiteral: number.value)
                    print("itemsInOrder: \(items)")
                } else {
                    print("\(number.key): \(number.value)")
                }
            }
        }
    })

However it is crashing and giving me this error:

Could not cast value of type '__NSArrayM' (0x2037d2608) to 'NSDictionary' (0x2037d21d0).

This is the code that I am trying to get:

itemsInOrder: [<__NSArrayM 0x281c060a0>({
    amount = "2";
    length = "5";
    height = "7";
    width = "10";
})]

Below is the Firebase json export

{
"TH000" : {
  "docUUIDStatus" : "Sent",
  "expectedShip" : "May 12, 2021",
  "itemsInOrder" : [ {
    "amountOrdered" : "2000 sq ft",
    "bevel" : "None",
    "floorLength" : "2-8",
    "floorWidth" : "4",
    "specialOrder" : "None",
    "specialOrderLength" : "",
    "status" : "completed"
  } ],
  "orderDate" : "May 11, 2021",
  "orderNumber" : "TH000",
  "orderNumberLowercased" : "th000",
  "orderTime" : "2:30:30 PM",
  "orderType" : "Order",
  "purchaseOrderNumber" : "TH10051",
  "purchaseOrderNumberLowercased" : "th10051",
  "status" : "completed"
 }
}
Willm132
  • 17
  • 9
  • I'm having a hard time parsing the data structure from the print output. Can you edit your question to show the data from `incomplete` as JSON text? You can get this by clicking the "Export JSON" link in the overflow menu (⠇) on your [Firebase Database console](https://console.firebase.google.com/project/_/database/data). – Frank van Puffelen May 13 '21 at 01:08
  • 1
    @FrankvanPuffelen I have edited the original question. – Willm132 May 13 '21 at 12:11

2 Answers2

1

What you have in itemsInOrder is an array of dictionaries, so to get the data from it, you either have to loop over number in a nested loop, or extract it with:

let items = number.value as! [[String:Any]]

Since I find the double [[ a bit hard to see at first glance, I actually prefer this equivalent syntax:

let items = number.value as! [Dictionary<String:Any>]

Unrelated to the actual problem: your data structure is a bit of an antipattern in Firebase, as reading any order also now means that you end up loading all items in that order. This becomes wasteful when you want to show a list of order names.

The more idiomatic data structure it so have two top-level nodes:

  • orders
  • orderItems

In the first you store the properties for each order, such as orderType and status, under the order ID. In the second node you store the list of order times, also under the order ID.

With that structure, your current query will require two steps:

  1. A query to determine the IDs of the orders with the requested status.
  2. Then an additional load for each order to get the items for that order.

This second step is not nearly as slow as you may think, as Firebase pipelines the requests over a single existing connection.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
0

The answer provided by Frank is spot on. I would like to suggest another approach.

We've found that DataSnapshots are super flexible and easy to work with - keeping data as a DataSnapshot for a long as possible provides easier access, especially when the structure is a bit deep. When casting to array's and dictionaries you end up with this kind of thing

[String: [String: [String: Any]]]

which is really cumbersome to work with and troubleshoot.

So here's the code that will read in the data as specified in the question. Note we never convert anything to a Dictionary and use arrays to guarantee ordering will be maintained from the retrieved DataSnapshots.

func readData() {
    let ref = self.ref.child("main_order").child("incomplete") //self.ref points to my firebase
    ref.observe(.value, with: { snapshot in //first node will be TH000
        let childSnapArray = snapshot.children.allObjects as! [DataSnapshot]
        for childSnap in childSnapArray {
            print("parent node: \(childSnap.key)")
            let status = childSnap.childSnapshot(forPath: "docUUIDStatus").value as? String ?? "No Status"
            print("  status: \(status)")
            let itemsInOrder = childSnap.childSnapshot(forPath: "itemsInOrder") as DataSnapshot
            let itemSnapArray = itemsInOrder.children.allObjects as! [DataSnapshot]
            print("     \(itemsInOrder.key)")
            for itemChildSnap in itemSnapArray {
                let amount = itemChildSnap.childSnapshot(forPath: "amountOrdered").value as? String ?? "No amount"
                let length = itemChildSnap.childSnapshot(forPath: "floorLength").value as? String ?? "No length"
                let width = itemChildSnap.childSnapshot(forPath: "floorWidth").value as? String ?? "No width"
                let status = itemChildSnap.childSnapshot(forPath: "status").value as? String ?? "No status"
                print("         ", amount, length, width, status)
            }
        }
    })
}

I dropped in a few print statements along the way to show the code execution sequence. The output to console is this

parent node: TH000
  status: Sent
     itemsInOrder
        2000 sq ft 2-8 4 completed

The one other thing is the array stored within itemsInOrder. It's not clear why that's an array and in general Arrays should be avoided in The Realtime Database if possible. There are usually better ways to structure the data. Arrays cannot be directly altered - if you want to insert or delete an element the entire array must be read in, modified and written back out.

So the above code may need to be slightly modified to handle the itemsInOrder node if there are multiple array elements, for example.

Jay
  • 34,438
  • 18
  • 52
  • 81