jsonterraformcloudformationdevopsiac

JSON for Infrastructure as Code: Terraform & CloudFormation

·9 min read·Advanced

Infrastructure as Code is mostly JSON underneath

You might write Terraform in HCL or a CloudFormation template in YAML, but underneath, both tools compile down to — and communicate in — JSON. Terraform's state file and plan output are JSON. CloudFormation templates can be written directly as JSON (YAML is just a friendlier syntax for the identical underlying structure). Knowing these shapes matters the moment you need to debug a broken deployment, write a script against terraform show -json, or generate a template programmatically.

Terraform state: JSON is the source of truth

Terraform's .tfstate file is JSON, and it's the single most important file in a Terraform-managed project — it's how Terraform knows what it already created, so it can compute a diff on the next plan.

json
{
  "version": 4,
  "terraform_version": "1.9.0",
  "resources": [
    {
      "type": "aws_s3_bucket",
      "name": "assets",
      "instances": [
        {
          "attributes": {
            "id": "my-app-assets-prod",
            "bucket": "my-app-assets-prod",
            "region": "us-east-1",
            "arn": "arn:aws:s3:::my-app-assets-prod"
          }
        }
      ]
    }
  ]
}

Every resource Terraform manages gets an entry here with its real, current attributes as last known — this is why manually editing infrastructure outside Terraform ("ClickOps") causes drift: the state file's JSON no longer matches reality, and Terraform's next plan will try to "fix" changes you made by hand.

Reading a Terraform plan as JSON

terraform plan normally prints colored, human-readable output, but terraform show -json (on a saved plan file) emits the exact same information as structured JSON — essential for CI pipelines that need to programmatically check "does this plan delete a database?" before allowing it to apply:

bash
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
json
{
  "resource_changes": [
    {
      "address": "aws_db_instance.primary",
      "type": "aws_db_instance",
      "change": {
        "actions": ["delete", "create"],
        "before": { "instance_class": "db.t3.micro" },
        "after": { "instance_class": "db.t3.large" }
      }
    }
  ]
}

An actions array of ["delete", "create"] means Terraform intends to destroy and recreate that resource — often surprising and sometimes catastrophic for a database. Many teams script exactly this check: parse plan.json, scan resource_changes for a delete action on anything stateful, and fail the CI pipeline requiring manual approval before it can proceed.

CloudFormation templates: JSON or YAML, same shape

A CloudFormation template describes AWS resources declaratively. Written as JSON:

json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "AssetsBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": "my-app-assets-prod",
        "VersioningConfiguration": { "Status": "Enabled" }
      }
    }
  },
  "Outputs": {
    "BucketArn": { "Value": { "Fn::GetAtt": ["AssetsBucket", "Arn"] } }
  }
}

The same template in YAML is more concise but describes an identical structureResources, each with a Type and Properties, plus Outputs referencing them. CloudFormation itself parses YAML templates into this same internal JSON-like model before processing.

Terraform's own JSON syntax (not just HCL)

Fewer developers know Terraform configuration itself can be written as JSON instead of HCL — useful for generating configs programmatically rather than templating HCL strings by hand:

json
{
  "resource": {
    "aws_s3_bucket": {
      "assets": {
        "bucket": "my-app-assets-prod"
      }
    }
  }
}

Saved as main.tf.json, this is functionally identical to the equivalent HCL — Terraform accepts either, and tools that generate infrastructure config dynamically (a script building resources from a list) often emit JSON rather than assembling HCL as text.

Practical debugging workflows

  • A plan looks wrong. Export it with terraform show -json and diff the before/after attributes for the resource in question using JSONKit's JSON Diff — far more precise than reading the colorized CLI summary for a resource with dozens of attributes.
  • State drifted. Pull the current state (terraform state pull) and compare a resource's attributes against what your HCL actually declares.
  • Writing a template generator. Sketch the exact JSON shape you need to emit first (in the JSON Formatter), then write the code that produces it — designing the target shape before the generator avoids a lot of trial and error.
  • Validating a hand-written JSON template. Run it through the JSON Validator before deploying — a trailing comma in a large CloudFormation JSON template is a common, easy-to-miss deploy blocker.

Frequently asked questions

Avoid it — use terraform state subcommands (mv, rm, import) instead. Hand-editing the JSON is easy to get subtly wrong in ways that corrupt Terraform's understanding of real infrastructure.

Some resource attributes can't be updated in place (they force a new resource) — check the provider's documentation for that attribute. The JSON plan's actions: ["delete", "create"] is Terraform being explicit about this, not a bug.

No — a CloudFormation template *declares desired infrastructure* (input), while a Terraform plan *describes proposed changes* (a diff against current state). Terraform's own .tf.json config format is the closer analog to a CloudFormation template.

Yes — since both represent the identical structure, most IDEs and the cfn-flip CLI tool convert between them losslessly. For manual inspection either way, JSONKit's JSON to YAML converter works for the JSON-format template.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.