Hedronite · Cert Lesson · HashiCorp · Fri 2026-10-02

Terraform Associate: encoding functions jsonencode and collection helpers

Encode documents. Merge tags. Name keys. Know which function you called.

Lesson Class: Cert (T2 · Terraform Associate 003 Obj. 8 · Pro-depth)
Vendor: HashiCorp
Verified: terraform console · Terraform 1.14.3 · pure functions
Paired Ops: CloudWatch log group retention + Rust census
Paired Dev: HCL jsonencode / templatefile
Grounding: Brikman pp.190-192 · Lab 20 · Lab 27
Encode Shelf
Name the consumer before picking the function.
jsonencode makes JSON. CloudWatch filters speak their own grammar.

<!-- hal:authoritative:yaml -->

Encode when the API wants a document. Merge when tags layer. Name keys when for_each needs a map. Know which function you called.

§I. Frame: Associate objectives for tonight

09-29 covered lifecycle. 09-26 covered type constraints. 09-23 covered data sources. 09-20 covered dynamic blocks. 09-17 covered ephemeral. 09-14 covered import and moved. 09-08 covered check blocks. Leave all of that.

Tonight is encoding and collection helpers: jsonencode, jsondecode, templatefile, keys, values, merge, tomap. Ops declares a CloudWatch log group and a metric filter. Dev draws the Encode Shelf. The drills below use terraform console so every expression is real and credential-free.

§II. Encoding shelf: three jobs

FunctionJobExam trap
jsonencode(value)HCL value → JSON stringDoes not produce CloudWatch filter grammar; produces JSON
jsondecode(string)JSON string → HCL valueInverse of encode; Lab 20 spine
templatefile(path, vars)File + map → stringInside modules use path.module; root-relative paths break

Named technique: Encode Shelf. Before you encode, name the consumer (JSON document, on-disk template, or domain grammar). Pick the matching function. Do not jsonencode a CloudWatch filter pattern and expect Logs to accept it.

§III. jsonencode: types survive

Open an empty directory, terraform console, and evaluate:

jsonencode({ retention = 30, never_expire = false, tags = ["app", "prod"] })

Terraform 1.14.3 prints a compact JSON string. Numbers stay numbers. false stays boolean. Lists stay arrays. That matters for IAM policies and for any attribute that parses JSON strictly.

Fact one. jsonencode("hello") yields "\"hello\"" (a JSON string, quotes included). String-in, string-out is still encoding.

Fact two. jsondecode(jsonencode(local.obj)) round-trips objects for exam purposes. Real configs usually encode once toward AWS and decode once from file() or a secret.

Fact three. Brikman 3e p.338 shows jsondecode on a secret string. Encoding is the write-side twin for documents you author in HCL.

§IV. Collection helpers: keys, values, merge, tomap

locals {
  base_tags = { team = "platform", env = "prod" }
  extra     = { service = "api", env = "staging" } # env collision
  merged    = merge(local.base_tags, local.extra)
}

merge overlays maps left to right. Later keys win. Here env becomes "staging". Lab 27 drills this for tag layers.

keys({ a = 1, b = 2 })    # ["a", "b"]  (lexicographic in current Terraform)
values({ a = 1, b = 2 })  # [1, 2]      (same key order as keys)
tomap({ a = "1", b = "2" }) # forces map type from an object constructor when needed

Rule one. keys / values order matches across a single map. Do not zip keys(A) with values(B).

Rule two. merge does not deep-merge nested objects. Nested maps replace as a whole.

Rule three. tomap is for type constraints and ambiguous constructors. Prefer an explicit map(...) type on variables when you control the interface (pair with 09-26 type constraints without redoing that lesson).

§V. CloudWatch surface on the exam

Ops sets retention_in_days and a metric filter pattern. Exam stems that touch tonight:

  1. Which function turns a map into an IAM policy JSON string? → jsonencode.
  2. **Which function renders user-data.sh with ${app}?** → templatefile.
  3. **A module calls templatefile("user-data.sh", ...) and breaks when called from another root.** → use "${path.module}/user-data.sh" (Brikman pp.190-192).
  4. Layer global tags under service tags with service winning on collision. → merge(global, service).
  5. **Build for_each keys from a map of log groups.** → keys(var.groups) or iterate the map directly with for_each = var.groups.

Trap: choosing jsonencode for a CloudWatch filter pattern that must be { $.level = "ERROR" }. That pattern is domain grammar. Dev Encode Shelf rule 3.

§VI. Five drills (answers below)

D1. jsonencode({ a = 1, b = true }) contains the substring true without quotes. True or false?

D2. merge({a=1},{a=2}).a evaluates to what?

D3. Inside a child module, the durable path form for a template next to main.tf is what expression?

D4. Name the inverse of jsonencode used in Lab 20 when reading a JSON file.

D5. Why is jsonencode({ level = "ERROR" }) the wrong producer for aws_cloudwatch_log_metric_filter.pattern?

Answers

D1. True. JSON boolean, not the string "true".

D2. 2. Later map wins.

D3. templatefile("${path.module}/…", { … }).

D4. jsondecode (often composed with file).

D5. Filter patterns use CloudWatch grammar, not a JSON object document. Encode Shelf: domain grammar consumer.

§VII. Close

Encoding functions move values across the HCL/JSON and HCL/file boundaries. Collection helpers shape maps for tags and for_each. Ops owns retention and the Rust census. Dev owns when to encode versus when to keep domain grammar as a string. Maghrib owns the quiz.

Related