Cannot convert object to map of any single type

Taking into account this code:

locals {
  pol_grp = {
    a = { pol1 = "arn:aws:iam::aws:policy/IAMReadOnlyAccess",
          pol2 = "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess" }
    b = { pol1 = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess" }
  }
  pol_arns_list= toset(flatten([
    for group in local.pol_grp : [
      for arn in group:
      arn ] ]))
  pols = tomap({
    for gk, group in local.pol_grp : gk => tomap({
      for pk, arn in group : pk => {
        arn    = arn } }) })
}
output "pol_grp" { value = local.pol_grp }
output "pol_arns_list" { value = local.pol_arns_list }
output "pols" { value = local.pols }

This works and will output this:

...
pols = tomap({
  "a" = tomap({
    "pol1" = {
      "arn" = "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
    }
    "pol2" = {
      "arn" = "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess"
    }
  })
  "b" = tomap({
    "pol1" = {
      "arn" = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
    }
  })
})

Leaving the object “b” empty, terraform returns error

...
  pol_grp = {
    a = { pol1 = "arn:aws:iam::aws:policy/IAMReadOnlyAccess",
          pol2 = "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess" }
    b = {}
  }
│ Error: Invalid function argument
│  135:   pols = tomap({
│  136:     for gk, group in local.pol_grp : gk => tomap({
│  137:       for pk, arn in group : pk => {
│  138:         arn    = arn
│  139:       }
│  140:     })
│  141:   })
│     ├────────────────
│     │ local.pol_grp is object with 2 attributes
│ Invalid value for "v" parameter: cannot convert object to map of any single
│ type.

I suppose because tomap of an empty object. I know that removing empty “b” object completely will avoid this error but for a matter of interest to know how terrafom work on this case I tried to limit the for loop to discard empty objects before tomap using length function without success:

...
  pols = tomap({
    for gk, group in local.pol_grp : gk => tomap({
      for pk, arn in group : pk => {
        arn    = arn
      }
    })
    if length(gk) > 0
  })
...

I’m doing probably wrong assumptions and so:

  1. What it means value for "v" parameter
  2. What it menas cannot convert object to map of any single type
  3. Am I doing wrong assumption on if condition param data type ? (not an object but a string) or something like that. How can I avoid error making more robust pols definition instead of imposing not empty objects ?

Hi @Roxyrob,

Can you verify that you are using the latest terraform release? The given code does convert the map type successfully.

Ah, I see the failure now.

What you are running into here is a limitation of Terraform’s ability to convert types implicitly, or maybe just a limitation of the tomap function itself. The v here is the tomap argument, and it’s failing to find a usable type for the map, probably because of the nesting.

The conversion is possible here, so I’m not sure offhand where the failure is happening, but a map(map(map(string))) is a single map type which satisfies the data given. You would get the desired output by routing the value through a module variable with the exact type defined, for example:

variable "mapify" {
  type = map(map(map(string)))
}

output "mapified" {
  value = var.mapify
}

This may be a fixable bug in the tomap function, however that is only going to be able to go so far. There are a number of difficult to resolve type inference cases already, and it will probably require a new method for type specification or in-line conversion to fully resolve.

Hi @jbardin,
probably I’m missing something here because I’m not sure about error interpetation as type conversion issue as this pol_grp definition works:

pol_grp = {
    a = { pol1 = "arn:aws:iam::aws:policy/IAMReadOnlyAccess",
          pol2 = "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess" }
    b = { pol1 = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess" }
  }

while this does not:

pol_grp = {
    a = { pol1 = "arn:aws:iam::aws:policy/IAMReadOnlyAccess",
          pol2 = "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess" }
    b = {}
  }

and the data structure is the same, with only the second code sample with an empty object (b={}).

For that very reason I thought the problem was related to presence of the empty object and I focused on the empty object exclusion attempt from the for loops (using if length(gk) > 0).

If I’m missing something and your are right, how and what I can cast to avoid the error in this sample (code inside locals section with no declared/defined variables).

I initially tried if condition on value (group) instead of key (gk), so not sure why in the first try the if on group didn’t work and I give a try on key (gk)…

Now I found that that solution works well using `if condition with length function on object value :

...
  pols = tomap({
    for gk, group in local.pol_grp : gk => tomap({
      for pk, arn in group : pk => {
        arn    = arn
      }
    })
    if length(group) > 0
  })
...

as expected the if condition exclude empty objects so tomap return expected output:

...
pol_grp = {
  "a" = {
    "pol1" = "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
    "pol2" = "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess"
  }
  "b" = {}
}
pols = tomap({
  "a" = tomap({
    "pol1" = {
      "arn" = "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
    }
    "pol2" = {
      "arn" = "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess"
    }
  })
})