hashicorp / hcl2

Former temporary home for experimental new version of HCL
https://github.com/hashicorp/hcl
Mozilla Public License 2.0
373 stars 66 forks source link

JSON syntax is too strict about nested block structures #12

Closed apparentlymart closed 6 years ago

apparentlymart commented 6 years ago

The JSON syntax is currently defined such that multiple blocks of a given type are defined by using a JSON array as the value of the innermost label, like this: (using a Terraform-like structure as an example)

{
  "provisioner": {
    "local-exec": [
      {
        "command": "echo command number 1"
      },
      {
        "command": "echo command number 2"
      }
    ]
  }
}

The above example is equivalent to the following in the native syntax, assuming a suitable schema that defines provisioner as a block with a single label:

provisioner "local-exec" {
  command = "echo command number 1"
}
provisioner "local-exec" {
  command = "echo command number 2"
}

To minimize the number of different possible expressions of a single HCL structure in JSON, the spec currently requires that array to be innermost, but in doing this the JSON syntax is unable to express a structure like the following:

provisioner "local-exec" {
  command = "echo command number 1"
}
provisioner "file" {
  source      = "foo"
  destination = "bar"
}
provisioner "local-exec" {
  command = "echo command number 2"
}

The closest we can get with the current JSON syntax specification is the following:

{
  "provisioner": {
    "local-exec": [
      {
        "command": "echo command number 1"
      },
      {
        "command": "echo command number 2"
      }
    ],
    "file": [
      {
        "source": "foo",
        "destination": "bar"
      }
    ]
  }
}

This is not equivalent because the relative ordering of the blocks has been lost in lowering to JSON. To represent the ordering unambiguously requires a more complex structure, which the current specification considers invalid:

{
  "provisioner": [
    {
      "local-exec": {
        "command": "echo command number 1"
      }
    },
    {
      "file": {
        "source": "foo",
        "destination": "bar",
      }
    },
    {
      "local-exec": {
        "command": "echo command number 2"
      }
    }
  ]
}

Here the array is sandwiched between the key representing the block type and the keys representing the labels, with each block in its own object so that the local-exec key can appear twice without forcing the two to merge.

This more-flexible model has the unfortunate side-effect that there is no longer a single canonical structure, but being able to properly represent all possible native syntax structures is the more important requirement.


To address this, the spec (and along with it, the reference implementation) must be amended so that any object whose keys are interpreted as block labels may instead be an array of such objects, with each array visited in order and contributing zero or more blocks to the final set.

Each of these listed label-defining objects may define multiple label keys if desired, in which case they are interpreted as required for a single label-defining object today, producing blocks in an undefined order. To ensure an exact ordering, the author must use only a single label key in each object and let the array define the ordering.

apparentlymart commented 6 years ago

This is addressed in eea3a14a71fc7d8358587de5bb93adae0a1a284d.