# Language Elements

When a Protean transform is executed, the result is determined by a specification (or spec), which is a JSON object whose nodes are the components of the spec. All specs (whether component or root) resolve to an output JSON object that may vary given different input JSON objects.

# Untyped Spec

The simplest example of a spec is formed from basic JSON nodes where no object node contains the key #type and no string contains a JSONPath. In this case, the output will be a verbatim copy of the spec and the input is completely ignored.

When a string does contain a JSONPath, that string node will resolve to the result of applying the JSONPath to the input JSON object, or other source objects depending on the syntax used (see Paths).

# Typed Spec

If an object does contain the key #type, the behaviour will be determined by the string value of #type (e.g. for_each), and the other key/value pairs will be used as parameters for the spec of that type instead of being rendered in the output directly.

Typically, the value of component spec parameters will themselves be component specs, allowing logical components and static templates to be plugged into one another and chained together arbitrarily.

A complete reference for all typed specs can be found in the Component Reference section.

# Paths

Proteus uses JSONPath to pull data from various sources into the output of a Protean transform. It provides idiosyncratic extensions to the basic syntax of JSONPath to achieve this.

# Atomic Paths

A single path is formed from three constituent parts. Listing them in the order they appear in the path, they are: flags, source specifier, and the base path.

// In this example, "[R,E]" are the flags, "$" is the source specifier and ".path.to.value" is the base path
[R,E]$.path.to.value

Flags take the form of a comma-delimited list of upper-case characters enclosed by square brackets. When a flag is present, it will activate certain behaviours. For example, the [R] flag will cause an exception to be thrown if a path is not resolvable in the input document, rather than returning null, as is the default behaviour.

The source specifier can be one of the following characters: $, %, & or #, which cause the path to be resolved against the input, properties map, arguments map or spec, respectively. The properties map is populated by the Glyph/transform request config, whereas the contents of the arguments map is driven by the internal logic of certain spec components.

The base path is the part that conforms to the natural syntax of JSONPath (except for omitting the leading $, which is subsumed by the source specifier). All ordinary JSONPath features are available such as predicates and recursive descent: [R]%.properties.path[0].with[?(@.predicate == 'something')]['and']..recursive..descent.

Proteus provides scoping by retaining a stack to which object references are pushed each time a scoping operation occurs (see Component Reference for details on scoping operations). For example, let the input be a JSON object that contains a list of objects and a string.

{
  "type": "object",
  "list": [
    {
      "name": "item_1"
    },
    {
      "name": "item_2"
    },
    {
      "name": "item_3"
    }
  ]
}

We can iterate over the list using a for_each component, but this will scope our paths to each entry of the list. However, if we use $$ instead of $, then it will always refer to the root of the input. E.g. the following spec

{
  "items": {
    "#type": "for_each",
    "values": "$.list",
    "spec": {
      "id": "$.name",
      "type": "$$.type"
    }
  }
}

will yield output

{
  "items": [
    {
      "id": "item-1",
      "type": "object"
    },
    {
      "id": "item-2",
      "type": "object"
    },
    {
      "id": "item-3",
      "type": "object"
    }
  ]
}

If we want to refer to a specific element of the stack, then include the index of the desired element between the dollar signs, enclosed in parentheses:

$(2)$.path.resolved.against.third.stack.element

Note that the index is resolved cyclically, so $(-1)$ refers to the last element of the stack (so is equivalent to $). It follows that $(0)$ is equivalent to $$.

We use the qualifier "atomic" when distinguishing this type of path from composite paths discussed in the next section.

# Composite Paths

We can mix static strings with those obtained from paths by embedding atomic paths in the string and surrounding each in curly braces. E.g.

{
  "composite_value": "This is a {$.adjective} example of {$$.verb}ing composite {[R]%.paths}"
}

These can be used to form data-dependent atomic paths using the [C] flag:

{
  "my_value": "[C]$.im.not.sure.what.{%.key}.i.want.yet"
}

In the above example, the path %.key would be resolved first (against properties in this case), then the result would be substituted into the outer path before it is resolved against input.

# Flags

Name Symbol Description
required [R] Throws a RequirementNotMetException if the path resolves to null.
composite [C] First resolve the base path as a composite path, then resolve the result as an atomic path.
literal [L] Resolves to the literal value of the base path.
empty [E] Resolves to an empty string instead of null if the path cannot be resolved.