20

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?

5 Answers 5

21

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
}
Sign up to request clarification or add additional context in comments.

1 Comment

what if our data is [[String: Any]] ? as I was trying its giving me index of bound exception ! var params = [[String: Any]]() for i in 0..data.count - 1 { params = [["otherKey":otherValue, "products": [] ]] for j in 0...data[i].products.count - 1 { let item : [String: Any] = ["prodcutName": data[i].product[j].name] var existingItems = params[i]["products"] as? [[String: Any]] ?? [[String: Any]]() // Here it crashes !!! "Index out of bound" existingItems.append(item) params[i]["products"] = existingItems } }
7

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

Comments

5

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) 
}

4 Comments

@JasonSturges I know, I have just show you what was the issue and where you can directly append array that was inside dictionary.
Really appreciate the clarity, and elegance of the [String: [[String: Any]]] typing.
Note that you may use data["items"]?.append(item) rather than data["items"]!.append(item) above; the latter will yield a runtime crash if there happens to be no value for the "items" key (even if we know it to be in this example), whereas the former will simply return an unused nil. Even if we know the "items" key to exist, I believe we should prefer the safe approach, especially when it come with no extra cost. Always a risk of reinforcing bad ! practice for beginner SO readers that do not realize that in this particular case/example, ! is safe, but otherwise, is generally not.
@dfri Thnx for suggestion, You provide the perfect solution for OP problem.
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.

Comments

0

Give you have an Array of items:[String]

He can also add this to the json

var items = ["string1", "Info 2","Text 4"]

data["items"] = items

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.