Transpose map of maps

I have a map of map of some resources as follows

foo = {
  westeurope = {
    Dev = {
      id = 1
      name = "foo"
    }
    Test = {
      id = 2
      name = "bar"
    }
  }
  northeurope = {
    Dev = {
      id = 3
      name = "Bob"
    }
    Test = {
      id = 4
      name = "Alice"
    }
  }
}

What I would like is the nesting to be reversed so I have

foo = {
  Dev = {
    westeurope = {
      id = 1
      name = "foo"
    }
    northeurope = {
      id = 3
      name = "Bob"
    }
 }
 Test = {
    westeurope = {
      id = 2
      name = "bar"
    }
    northeurope = {
      id = 4
      name = "Alice"
    }
  }
}

I can obviously for_each over the map, but can’t see how to re-project it given that the depth of the second map is unknown.

Hi @phatcher ,
I started transposing this within terraform, however didn’t come to an end (yet :slight_smile: ).
I’m wondering if offloading it to jq might be the “better” approach.

Hi @phatcher,

I think I’d consider breaking this problem down into a few steps.

The transpose function can perform the high-level operation you want here, but its signature is not exactly compatible with the shape of your data structure. To address that, we can first construct an intermediate structure that is transpose-compatible:

variable "foo" {
  type = map(map(object({
    id   = number
    name = string
  })))
}

locals {
  foo_region_envs = {
    for region, envmap in var.foo : region => [
      for env, obj in envmap : env
    ]
  }
}

This intermediate data structure would be like the following:

{
  westeurope  = ["Dev", "Test"]
  northeurope = ["Dev", "Test"]
}

That matches the sort of structure transpose expects, so we can now use transpose to invert it:

locals {
  foo_env_regions = transpose(local.foo_region_envs)
}

That should give something like the following:

{
  Dev = ["westeurope", "northeurope"]
  Test = ["westeurope", "northeurope"]
}

Finally, we can cross-reference this with the original data structure to bring it all back together into the shape you were looking for:

locals {
  transposed_foo = {
    for env, regions in local.foo_env_regions : env => {
      for region in regions : region => var.foo[region][env]
    }
  }
}

It would be possible in principle to combine all of those steps together into a single complex expression, but I think the result would be quite hard to read and so I’d suggest keeping it split over separate steps and using good variable names to make it easier for a future maintainer to understand what each of the steps is producing.

However, when writing it all out together we can at least put all of the local values together in one block. Here’s the complete example, all together:

variable "foo" {
  type = map(map(object({
    id   = number
    name = string
  })))
}

locals {
  foo_region_envs = {
    for region, envmap in var.foo : region => [
      for env, obj in envmap : env
    ]
  }
  foo_env_regions = transpose(local.foo_region_envs)
  transposed_foo = {
    for env, regions in local.foo_env_regions : env => {
      for region in regions : region => var.foo[region][env]
    }
  }
}

2 Likes