5

I'm strangely having trouble with this simple Twig merge problem and can't find anything online. Simplified, let's say you had:

{% set users = [
    {name: 'Bill', age: 25},
    {name: 'Tim', age: 30},
    {name: 'John', age: 32}
] %}

How would you change, for example, Tim's age from 30 to 31? I've tried:

{% set users = users[1]|merge({age: 31}) %}
{{ users[1].name }}: {{ users[1].age }}

But this doesn't work.

4 Answers 4

12

Solution

This won't work in Twig as presented in the question (see why below). Instead, users would need to be changed to an associative array (or "hash" in Twig terminology) with keys that aren't integers, and you'd have to insert a merge inside a merge to change the value:

{% set users = {
    person0: {name: 'Bill', age: 25},
    person1: {name: 'Tim', age: 30},
    person2: {name: 'John', age: 32}
} %}

{% set users = users|merge({ person1: users.person1|merge({age: 1000}) }) %}

{% for key, user in users %}
    {{ key }}: {{ user.name }}, {{ user.age }}<br>
{% endfor %}

...which now outputs:

person0: Bill, 25
person1: Tim, 1000
person2: John, 32

If you need to change the keys' integers dynamically, you will write it like this using Twig's attribute function and using brackets:

{% set loopIndex = 1 %}
{% set users = users|merge({ ('person' ~ loopIndex): attribute(users, 'person' ~ loopIndex)|merge({age: 1000}) }) %}

The Reason Why the Question's Array Doesn't Work

Twig's merge filter uses PHP's array_merge function, and according to PHP's docs when using array_merge:

If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.

So trying to overwrite an existing hash in the original array

{% set users = [
    {name: 'Bill', age: 25},
    {name: 'Tim', age: 30},
    {name: 'John', age: 32}
] %} 

...will just add the hash as a new, fourth item in the array, not replace the second one. To demonstrate this clearer, we can rewrite users as a hash like this (it's considered the same as above):

{% set users = {
    0: {name: 'Bill', age: 25},
    1: {name: 'Tim', age: 30},
    2: {name: 'John', age: 32}
} %}

By trying to target the key 1 to change its hash and output users:

{% set users = users|merge([ users.1|merge({age: 1000}) ]) %}

{% for key, user in users %}
    {{ key }}: {{ user.name }}, {{ user.age }}<br>
{% endfor %}

...we get this list:

0: Bill, 25
1: Tim, 30
2: John, 32
3: Tim, 1000

A new "Tim" user will get appended even if you make the keys strings like '0', '1', etc, or using Twig's attribute function. An issue for this was posted in Twig's github and the conversation explains it some more. You can only change a value if it's an array with a list of single values (not a hash) or if it's a key/value hash, and the key can't be an integer.

5

This could also be accomplished with the map filter, if you’re running Craft 3.1.29 or later:

{% set users = [
    {name: 'Bill', age: 25},
    {name: 'Tim', age: 30},
    {name: 'John', age: 32}
] %}

{% set users = users|map(u => u.name == 'Tim' ? u|merge({age: 31}) : u) %}
1
  • I like this solution. Thank you! Oct 20, 2022 at 13:59
0

A little bit nasty but you could do it like this (I could not find anything else)
Create this function in your plugins Variable

/**
 * @param array $array The array to modify
 * @param string $path The path in the array
 * @param mixed $value The value to set
 * @param string $delimiter The separator for the path
 * @return The new array
 */
function setNestedArrayValue($array, $path, $value, $delimiter = '/') {
    $pathParts = explode($delimiter, $path);

    $current = &$array;
    foreach($pathParts as $key) {
        $current = &$current[$key];
    }
    $current = $value;

    return $array;
}

Then you have this in your twig template

 {% set users = [
    {name: 'Bill', age: 25},
    {name: 'Tim', age: 30},
    {name: 'John', age: 32}
] %}

{% set users = craft.pluginHandle.setNestedArrayValue(users, '0/age', 40) %}
5
  • I was hoping to do this in Twig. If the answer is that it can't be done in Twig, that would be good to have documented somewhere, and I could accept that as the answer. Maybe an alternative Twig array setup would be nice to see, even if it means sacrificing some flexibility in my example. I think a plugin is a bit overkill.
    – Ryan
    Oct 27, 2017 at 21:17
  • Why overkill? It takes only about 1 min to create the plugin. Don't get me wrong, I don't want to defend my answer or something, I just don't understand why so many people refuse to use a plugin in craft. A plugin doesn't even make your code noticeable slower. Oct 27, 2017 at 21:21
  • Well although a plugin is a solution, it's not really the answer to the question. And although I understand that Twig was not meant for heavy logic, sometimes you just need to change a value in a custom associative array like this and I don't want to add another plugin to the group just to do a simple task if I can help it. If this were a part of a greater chunk of logic, then sure, write a plugin and do it in php.
    – Ryan
    Oct 29, 2017 at 18:24
  • Well then you can't solve your problem. It's not intended to do such things in twig. So the answer is: you can't do it in twig alone Oct 29, 2017 at 18:26
  • I think you can do this in Twig, but you have to adjust the users array to be a hash. A dynamic solution is presented in my answer I posted.
    – Ryan
    Oct 29, 2017 at 21:53
-1

Did you try something like:

{% set users[1]['age'] = 31 %}
1
  • No, this results in an error saying Unexpected token "punctuation" of value "[" ("end of statement block" expected). Twig makes you use its merge filter to change values in an array or hash (associative array).
    – Ryan
    Oct 29, 2017 at 21:49

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.