Append to array in [String: Any] dictionary structure

Question

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?


Show source
| swift   | swift3   | arrays   | dictionary   2017-01-07 07:01 3 Answers

Answers ( 3 )

  1. 2017-01-07 07:01

    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
    }
    
  2. 2017-01-07 07:01

    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) 
    }
    
  3. 2017-01-07 09:01

    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"]       */
    
◀ Go back