Parsing and manipulating json data in locals

Hi all,

I have another question about JSON parsing. Imagine I have this JSON:

{
    "all_dogs" :[
        {
            "name": "foo", 
            "groups": ["morning", "evening"]
        }, 
        {
            "name": "bar",
            "groups": ["evening", "saturday"]
        }, 
        {
            "name": "feet", 
            "groups": ["afternoon"]
        }
    ]
}

I can extract all the groups like this:

locals {
  all_dogs = jsondecode(file("${path.module}/dogs.json"))

  all_groups = toset(flatten(local.all_dogs.all_dogs[*].groups))
}

Now, I’m trying to create a MAP. Each group is a key of the map and values of the maps are the different dogs in that group.
So I would like to create a map like this:
afternoon = [feet]
evening= [foo, bar]
morning= [foo]
saturday= [bar]

Later on, I would like to use that map to provision some resources. Is this possible?

Hi @amsou,

This requirement is a little more specialized than just flattening all values into a single set, and so there isn’t a built-in function to achieve it but you can get this sort of result using for expressions, which are a more general approach to projecting and filtering data structures.

I think there are a few different ways to get the result you want here, but the one that I find easiest to think about is to split the work into two steps:

  1. Flatten the data structure into pairs of a single name and a single group, instead of a single name and multiple groups.
  2. Project those pairs into a map using grouping mode to allow for there being multiple names for the same group.

Here’s one way to achieve step 1:

locals {
  dog_group_pairs = toset(flatten([
    for d in local.all_dogs.all_dogs : [
      for g in d.groups : {
        name  = d.name
        group = g
      }
    ]
  ]))
}

The result of this expression is a set of objects that each represent a pair of one name and one group, like this:

toset([
  { name = "foo", group = "morning" },
  { name = "foo", group = "evening" },
  { name = "bar", group = "evening" },
  { name = "bar", group = "saturday" },
  { name = "feet", group = "afternoon" },
])

Now we can use this result to achieve step 2, by projecting this set into a map using the group names as the grouping keys:

locals {
  group_dogs = tomap({
    for pair in local.dog_group_pairs :
    pair.group => pair.name...
  })
}

The ... symbol after pair.name is what activates the grouping mode of the for expression. Without that, Terraform would raise an error if two elements produced the same key, but with grouping mode enabled Terraform will instead make the map elements all be lists so that multiple input elements with the same key can be grouped together under that key.

The result of this should be the data structure you requested:

tomap({
  morning   = ["foo"]
  evening   = ["foo", "bar"]
  saturday  = ["bar"]
  afternoon = ["feet"]
})
1 Like

Thank you very much! This is very clear!! I’ve learned so much in your message!!