collections.Where is always empty for map of maps

Problem

When running collections.Where on a map of maps an empty result is returned instead of a filtered map.

Example

Given data/users.yaml as

user1:
   name: A
   git: foo
user2:
   name: B
   git: baz

and a template code of

{{ collections.Where .Site.Data.users "name" "A" }}

I would expect that the map

user1:
   name: A
   git: foo

is returned. Instead an empty map is returned.

Why I think that this is a problem

The documentation describes collections.Where as

Returns the given collection, removing elements that do not satisfy the comparison condition.

As collections are arrays, slices or maps I would have expected this to work. The description of the collection parameter also does not further specify what types of collections are allowed.

I also had a quick look into the code and it seems to me that this case is simply not implemented. Lists of maps and maps with lists (that have maps as entries) as values work. Maps with maps directly as values don’t work and always return an empty map.

Am I missing something here?

Solutions

Granted that I am not missing something I see 3 ways that this problem could be solved or alleviated:

  • Implement support for this case. I’d also be willing to try to contribute this case. (Preferred)
  • Update the docs to specify more clearly what types of collections are supported.
  • Emit some warning that the collection contains values of an unsupported type

I agree that the documentation for where should limit the definition of collection to arrays/slices.

I have mixed feelings about how the where function should work. To me, the where function should behave like the SQL WHERE clause, operating on rows in a table.

Although this sort of looks like rows in a table:

{
  "user1": {
    "git": "foo",
    "name": "A"
  },
  "user2": {
    "git": "baz",
    "name": "B"
  }
}

…this clearly does not:

{
  "product2": {
    "name": "B",
    "price": 67.42
  },
  "user1": {
    "git": "foo",
    "name": "A"
  }
}

So… undecided.

This may not apply to your project, but the sort function eats map keys, so this:

{{ where (sort site.Data.users) "name" "A" }}

returns this data structure:

[
  {
    "git": "foo",
    "name": "A"
  }
]

I have changed this:

image

To this:

image

Thanks for updating the docs. The clarification clears up the confusion that I had.

The documentation unfortunately is too restrictive now. Maps of collections of maps do work. In this setup map entries are kept if any element of the collection matches the filter.

Example

With

user1:
  - name: A
    git: foo
  - name: C
    git: bar
user2:
  - name: B
    git: baz
user3:
  - name: A
    git: lorem

and

{{ collections.Where .Site.Data.users "name" "A" }}

the result is

user1:
  - name: A
    git: foo
  - name: C
    git: bar
user3:
  - name: A
    git: lorem

I see your point. maps often have elements whose structure is different, while it feels natural for collections to have elements with the same structure. Though this is guaranteed for neither.

My use case is basically that of a collection of maps. But I wanted easier access to the elements without having to go through where and deal with the case that I might get more than one element.


If one really needs a where on a map of maps this behavior can be achieved with a partial. I have attached an example where the operator is equality.

{{ $result := dict }}
{{ range $k, $v := .Obj }}
   {{ if  eq (index $v $.Key) $.Value }}
      {{ $result = merge $result (dict $k $v) }}
   {{ end }}
{{ end }}
{{ return $result }}

and then use it as

{{ partial "where" (dict "Obj" .Site.Data.users "Key" "name" "Value" "A") }}