Append to array in [String: Any] dictionary structure

50,338

Solution 1

The type of data[Items] isn't Array but actually Array<[String: Any]>.

You could probably squeeze this into fewer steps, but I prefer the clarity of multiple steps:

var data: [String: Any] = [
    "key1": "example value 1",
    "key2": "example value 2",
    "items": []
]

for index in 1...3 {

    let item: [String: Any] = [
        "key": "new value"
    ]

    // get existing items, or create new array if doesn't exist
    var existingItems = data["items"] as? [[String: Any]] ?? [[String: Any]]()

    // append the item
    existingItems.append(item)

    // replace back into `data`
    data["items"] = existingItems
}

Solution 2

If you prefer brevity over clarity, you can perform this operation in a single line, making use of the nil coalescing operator and the + operator for RangeReplaceableCollection (to which Array conforms), the latter used for the "append" step (in fact constructing a new collection which will the replace the existing one when replacing the value of data["items"]).

// example setup
var data: [String: Any] = [
    "key1": "example value 1",
    "key2": "example value 2",
    "items": []
]

// copy-mutate-replace the "items" array inline, adding a new dictionary
data["items"] = (data["items"] as? [[String: Any]] ?? []) + [["key": "new value"]]

print(data)
/* ["key2": "example value 2", 
    "items": [["key": "new value"]], 
    "key1": "example value 1"]       */

// add another dictionary to the "items" array
data["items"] = (data["items"] as? [[String: Any]] ?? []) + [["key": "new value"]]

print(data)
/* ["key2": "example value 2",
 "items": [["key": "new value"], ["key": "new value"]],
 "key1": "example value 1"]       */

Solution 3

subscript with Dictionary always return optional(?) instance because it may possible that key is not exist in Dictionary. Now you have declare your dictionary as [String:Any] means type of key is String and value is Any.

Now when you write data["item"] it will return you Any? that is not Array so you can not called append and it will return immutable value so you can't directly mutate it.

So you need to type cast it to Array of Dictionary and stored it to instance and append that instance after that replace that object in your dictionary using reassigning it.

for index in 1...3 {
    let item: [String: Any] = [
        "key": "new value"
    ]

    var array = data["items"] as? [[String:Any]] ?? [[String:Any]]()
    array.append(item)
    data["items"] = array
}

Now thing is got lot easier if you have Dictionary like [String: [[String:Any]]] for eg.

var data: [String: [[String:Any]]] = [
    "items": []
]

for index in 1...3 {
    let item: [String: Any] = [
        "key": "new value"
    ]
    data["items"]?.append(item) 
}

Solution 4

I'm not entirely sure if this works on the [String: Any] type of dictionary but I also highly recommend you create a struct instead of using "Any" for your dictionary. It'll help run things a lot smoother.

On the other hand, I wanted to mention, you can try something like:

yourDictionary["Key", default: []].append(item)

This will initialize an array for that "Key" that will be used in the dictionary if there isn't already an array initialized. Without this, you'd have to check and initialize the array yourself which ends up being some repetitive code.

Hope this helps.

Share:
50,338
Jason Sturges
Author by

Jason Sturges

Avant-garde experimental artist - creative professional leveraging technology for immersive experiences.

Updated on July 09, 2022

Comments

  • Jason Sturges
    Jason Sturges almost 2 years

    Assembling a data payload passed to GRMustache.swift for rendering mustache templates, I'm in a scenario where I need to append data to an array previously defined in the dictionary.

    My data structure starts off as:

    var data: [String: Any] = [
        "key1": "example value 1",
        "key2": "example value 2",
        "items": [
            // I need to append here later
        ]
    ]
    

    The items key pair is a collection I need to append later within a loop.

    To add to the data["items"] array, I'm trying something like:

    for index in 1...3 {
        let item: [String: Any] = [
            "key": "new value"
        ]
    
        data["items"].append(item)
    }
    

    This errors, as value of type Any? has no member append, and binary operator += cannot be applied to operands of type Any? and [String : Any].

    This makes sense, as I need to cast the value to append; however, I can't mutate the array.

    Casting to array, whether forcing downcast gives the error:

    (data["items"] as! Array).append(item)
    

    'Any?' is not convertible to 'Array<_>'; did you mean to use 'as!' to force downcast?

    Cannot use mutating member on immutable value of type 'Array<_>'

    Seems like my cast is wrong; or, perhaps I'm going about this in the wrong way.

    Any recommendation on how to fill data["items"] iteratively over time?