hcl/hcl/json/spec.md

8.8 KiB

zcl JSON Syntax Specification

This is the specification for the JSON serialization for hcl. zcl is a system for defining configuration languages for applications. The zcl information model is designed to support multiple concrete syntaxes for configuration, and this JSON-based format complements the native syntax by being easy to machine-generate, whereas the native syntax is oriented towards human authoring and maintenence.

This syntax is defined in terms of JSON as defined in RFC7159. As such it inherits the JSON grammar as-is, and merely defines a specific methodology for interpreting JSON constructs into zcl structural elements and expressions.

This mapping is defined such that valid JSON-serialized zcl input can be produced using standard JSON implementations in various programming languages. Parsing such JSON has some additional constraints not beyond what is normally supported by JSON parsers, though adaptations are defined to allow processing with an off-the-shelf JSON parser with certain caveats, described in later sections.

Structural Elements

The zcl language-agnostic information model defines a body as an abstract container for attribute definitions and child blocks. A body is represented in JSON as a JSON object.

As defined in the language-agnostic model, body processing is done in terms of a schema which provides context for interpreting the body's content. For JSON bodies, the schema is crucial to allow differentiation of attribute definitions and block definitions, both of which are represented via object properties.

The special property name "//", when used in an object representing a zcl body, is parsed and ignored. A property with this name can be used to include human-readable comments. (This special property name is not processed in this way for any other zcl constructs that are represented as JSON objects.)

Attributes

Where the given schema describes an attribute with a given name, the object property with the matching name — if present — serves as the attribute's definition.

When a body is being processed in the dynamic attributes mode, each object property serves as an attribute definition for the attribute whose name matches the property name.

The value of an attribute definition property is interpreted as an expression, as described in a later section.

Given a schema that calls for an attribute named "foo", a JSON object like the following provides a definition for that attribute:

{
  "foo": "bar baz"
}

Blocks

Where the given schema describes a block with a given type name, the object property with the matching name — if present — serves as a definition of zero or more blocks of that type.

Processing of child blocks is in terms of nested JSON objects and arrays. If the schema defines one or more labels for the block type, a nested object is required for each labelling level, with the object keys serving as the label values at that level.

After any labelling levels, the next nested value is either a JSON object representing a single block body, or a JSON array of JSON objects that each represent a single block body. Use of an array accommodates the definition of multiple blocks that have identical type and labels.

Given a schema that calls for a block type named "foo" with no labels, the following JSON objects are all valid definitions of zero or more blocks of this type:

{
  "foo": {
    "child_attr": "baz"
  }
}
{
  "foo": [
    {
      "child_attr": "baz"
    },
    {
      "child_attr": "boz"
    }
  ]
}
{
  "foo": []
}

The first of these defines a single child block of type "foo". The second defines two such blocks. The final example shows a degenerate definition of zero blocks, though generators should prefer to omit the property entirely in this scenario.

Given a schema that calls for a block type named "foo" with two labels, the extra label levels must be represented as objects as in the following examples:

{
  "foo": {
    "bar": {
      "baz": {
        "child_attr": "baz"
      },
      "boz": {
        "child_attr": "baz"
      }
    },
    "boz": {
      "baz": {
        "child_attr": "baz"
      },
    }
  }
}
{
  "foo": {
    "bar": {
      "baz": {
        "child_attr": "baz"
      },
      "boz": {
        "child_attr": "baz"
      }
    },
    "boz": {
      "baz": [
        {
          "child_attr": "baz"
        },
        {
          "child_attr": "boz"
        }
      ]
    }
  }
}

Where multiple definitions are included for the same type and labels, the JSON array is always the value of the property representing the final label, and contains objects representing block bodies. It is not valid to use an array at any other point in the block definition structure.

Expressions

JSON lacks a native expression syntax, so the zcl JSON syntax instead defines a mapping for each of the JSON value types, including a special mapping for strings that allows optional use of arbitrary expressions.

Objects

When interpreted as an expression, a JSON object represents a value of a zcl object type.

Each property of the JSON object represents an attribute of the zcl object type. The object type is constructed by enumerating the JSON object properties, creating for each an attribute whose name exactly matches the property name, and whose type is the result of recursively applying the expression mapping rules.

An instance of the constructed object type is then created, whose values are interpreted by again recursively applying the mapping rules defined in this section.

It is an error to define the same property name multiple times within a single JSON object interpreted as an expression.

Arrays

When interpreted as an expression, a JSON array represents a value of a zcl tuple type.

Each element of the JSON array represents an element of the zcl tuple type. The tuple type is constructed by enumerationg the JSON array elements, creating for each an element whose type is the result of recursively applying the expression mapping rules. Correspondance is preserved between the array element indices and the tuple element indices.

An instance of the constructed tuple type is then created, whose values are interpreted by again recursively applying the mapping rules defined in this section.

Numbers

When interpreted as an expression, a JSON number represents a zcl number value.

zcl numbers are arbitrary-precision decimal values, so an ideal implementation of this specification will translate exactly the value given to a number of corresponding precision.

In practice, off-the-shelf JSON parsers often do not support customizing the processing of numbers, and instead force processing as 32-bit or 64-bit floating point values with a potential loss of precision. It is permissable for a zcl JSON parser to pass on such limitations if and only if the available precision and other constraints are defined in its documentation. Calling applications each have differing precision requirements, so calling applications are free to select an implementation with more limited precision capabilities should high precision not be required for that application.

Boolean Values

The JSON boolean values true and false, when interpreted as expressions, represent the corresponding zcl boolean values.

The Null Value

The JSON value null, when interpreted as an expression, represents a zcl null value of the dynamic pseudo-type.

Strings

When intepreted as an expression, a JSON string may be interpreted in one of two ways depending on the evaluation mode.

If evaluating in literal-only mode (as defined by the syntax-agnostic information model) the literal string is intepreted directly as a zcl string value, by directly using the exact sequence of unicode characters represented. Template interpolations and directives MUST NOT be processed in this mode, allowing any characters that appear as introduction sequences to pass through literally:

"Hello world! Template sequences like ${ are not intepreted here."

When evaluating in full expression mode (again, as defined by the syntax- agnostic information model) the literal string is instead interpreted as a standalone template in the zcl Native Syntax. The expression evaluation result is then the direct result of evaluating that template with the current variable scope and function table.

"Hello, ${name}! Template sequences are interpreted in full expression mode."

In particular the Template Interpolation Unwrapping requirement from the zcl native syntax specification must be implemented, allowing the use of single-interpolation templates to represent expressions that would not otherwise be representable in JSON, such as the following example where the result must be a number, rather than a string representation of a number:

"${ a + b }"