Metanorma: Aequitate Verum

Json2Text plugin: Content from JSON data

Purpose

When managing large amounts of data with identical data structures, such as registry data or requirements, Metanorma supports a templating mechanism for generating structured text using data structures as input.

The json2text block lets you generate document elements directly from a separate JSON file into the document. [added in https://github.com/metanorma/metanorma-standoc/releases/tag/v1.5.3]

Expressions

json2text supports all Liquid syntax expressions, including:

  • variables, variable assignment

  • flow control (if/case)

  • filters

  • loops

See here for the full description of Liquid tags and expressions.

Syntax

Defining the block

A json2text block is created with the following syntax.

Block opening and closing is demarcated by an open block syntax (--) or the [source] block syntax (---- or more -).

[json2text,{JSON file path},{self-defined context name}]
----
this is content within the json2text block!
----

Where:

  • content within the block is called the “template”;

  • {JSON file path} is the location of the JSON file that contains data to be loaded. Location of the JSON file is computed relative to the source directory that [json2text] is used (e.g., if [json2text,data.json,data] is invoked in an .adoc file located at /foo/bar/doc.adoc, the data file is expected to be found at /foo/bar/data.json);

  • {self-defined context name} is the name where the JSON data read from the JSON file can be accessed with.

Interpolation

json2text accepts string interpolation of the following forms:

  1. {variable}: as in AsciiDoc syntax;

  2. {{ variable }}, {% if/else/for/case %}: basic Liquid tags and expressions are supported.

The value within the curly braces will be interpolated by json2text.

Where:

  • In {variable}({{variable}}), variable is the name of the variable or AsciiDoc attribute.

  • The location of {variable}({{variable}}) in text will be replaced with the value of variable.

  • Evaluation order will be first from the defined context, then of the Metanorma AsciiDoc document.

Accessing object values

Object values are accessed via the . (dot) separator.

Example 1. Example of accessing object values

Given:

strings.json

{
  "foo": "bar",
  "dead": "beef"
}

And the block:

[json2text,strings.json,data]
----
I'm heading to the {{data.foo}} for {{data.dead}}.
----

The file path is strings.json, and context name is data. {{data.foo}} evaluates to the value of the key foo in data.

Will render as:

I'm heading to the bar for beef.

Accessing arrays

Length

The length of an array can be obtained by {{arrayname.size}}.

Example 2. Example of accessing arrays

Given:

strings.json

[
  "lorem",
  "ipsum",
  "dolor"
]

And the block:

[json2text,strings.json,data]
----
The length of the JSON array is {{data.size}}.
----

The file path is strings.json, and context name is data. {{data.size}} evaluates to the length of the array using liquid size filter.

Will render as:

The length of the JSON array is 3.

Enumeration and context

The following syntax is used to enumerate items within an array:

{% for item in array_name %}
  ...content...
{% endfor %}

Where:

  • array_name is the name of the existing context that contains array data

  • item is the current item within the array

Within an array enumerator, the following expressions can be used:

  • {{forloop.index0}} gives the zero-based position of the item item_name within the parent array

  • {{forloop.length}} returns the number of iterations of the loop.

  • {{forloop.first}} returns true if it’s the first iteration of the for loop. Returns false if it is not the first iteration.

  • {{forloop.last}} returns true if it’s the last iteration of the for loop. Returns false if it is not the last iteration.

  • {{array_name.size}} gives the length of the array array_name

  • {{array_name[i]}} provides the value at index i (zero-based: starts with 0) in the array array_name; -1 can be used to refer to the last item, -2 the second last item, and so on.

Example 3. Example of iterating through a for loop

Given:

strings.json

[
  "lorem",
  "ipsum",
  "dolor"
]

And the block:

[json2text,strings.json,arr]
----
{% for item in arr %}
=== {{forloop.index0}} {item}

This section is about {item}.

{endfor}
----

Where:

  • file path is strings.json

  • current context within the enumerator is called item

  • {{forloop.index0}} gives the zero-based position of item item in the parent array arr.

Will render as:

=== 0 lorem

This section is about lorem.

=== 1 ipsum

This section is about ipsum.

=== 2 dolor

This section is about dolor.

Accessing objects

Size

Similar to arrays, the number of key-value pairs within an object can be obtained by {{objectname.size}}.

Example 4. Example of accessing an object

Given:

object.json

{"name":"Lorem ipsum","desc":"dolor sit amet"}

And the block:

[json2text,object.json,data]
----
=== {{data.name}}

{{data.desc}}
----

The file path is object.json, and context name is data. {{data.size}} evaluates to the size of the object.

Will render as:

=== Lorem ipsum

dolor sit amet

Enumeration and context

The following syntax is used to enumerate key-value pairs within an object:

{% for item in object_name %}
  {{item[0]}}, {{item[1]}}
{% endfor %}

Where:

  • object_name is the name of the existing context that contains the object

  • {{item[0]}} contains the key of the current enumrated object

  • {{item[1]}} contains the value

  • {% endfor %} indicates where the object enumeration block ends

Example 5. Example of iterating through an object

Given:

object.json

{
  "name": "Lorem ipsum",
  "desc": "dolor sit amet"
}

And the block:

[json2text,object.json,my_item]
----
{% for item in my_item %}
=== {{item[0]}}

{{item[1]}}

{% endfor %}
----

Where:

  • file path is object.json

  • current key within the enumerator is called item[0]

  • {{item[0]}} gives the key name in the current iteration

  • {{item[1]}} gives the value in the current iteration

Will render as:

=== name

Lorem ipsum

=== desc

dolor sit amet

Moreover, the keys and values attributes can also be used in object enumerators.

Example 6. Example of using keys and values in object enumeration

Given:

object.json

{
  "name": "Lorem ipsum",
  "desc": "dolor sit amet"
}

And the block:

[json2text,object.json,item]
----
.{{item.values[1]}}
[%noheader,cols="h,1"]
|===
{% for elem in item %}
| {{elem[0]}} | {{elem[1]}}

{% endfor %}
|===
----

Where:

  • file path is object.json

  • current key within the enumerator is called key

  • {{item[1]}} gives the value of key in the current iteration the parent array my_item.

  • {{item.values[1]}} gives the value located at the second key within item

Will render as:

.dolor sit amet

[%noheader,cols="h,1"]
|===
| name | Lorem ipsum
| desc | dolor sit amet
|===

There are several optional arguments to the for tag that can influence which items you receive in your loop and what order they appear in:

  • limit:<INTEGER> lets you restrict how many items you get.

  • offset:<INTEGER> lets you start the collection with the nth item.

  • reversed iterates over the collection from last to first.

Example 7. Example of using limit and offset attributes in a for loop

Given:

strings.json

[
  "lorem",
  "ipsum",
  "dolor",
  "sit",
  "amet"
]

And the block:

[json2text,strings.json,items]
----
{% for elem in items limit:2 offset:2 %}
{{item}}
{% endfor %}
----

Where:

  • file path is strings.json

  • limit - how many items we shoudl take from the array

  • offset - zero-based offset of item from which start the loop

  • {{item}} gives the value of item in the array

Will render as:

dolor sit

Advanced examples

With the syntax of enumerating arrays and objects we can now try more powerful examples.

Array of objects

Example 8. Advanced example of accessing an array of objects

Given:

array_of_objects.json

[{
  "name": "Lorem",
  "desc": "ipsum",
  "nums": [2]
}, {
  "name": "dolor",
  "desc": "sit",
  "nums": []
}, {
  "name": "amet",
  "desc": "lorem",
  "nums": [2, 4, 6]
}]

And the block:

[json2text,array_of_objects.json,ar]
----
{% for item in ar %}

{{item.name}}:: {{item.desc}}

{% for num in item.nums %}
- {{item.name}}: {{num}}
{% endfor %}

{% endfor %}
----

Notice we are now defining multiple contexts:

  • using different context names: ar, item, and num

Will render as:

Lorem:: ipsum

- Lorem: 2

dolor:: sit

amet:: lorem

- amet: 2
- amet: 4
- amet: 6

An array with interpolated file names (for AsciiDoc consumption)

json2text blocks can be used for pre-processing document elements for AsciiDoc consumption.

Example 9. Advanced example of using interpolated file names

Given:

strings.json

{
  "prefix": "doc-",
  "items": ["lorem", "ipsum", "dolor"]
}

And the block:

[json2text,strings.json,json]
------
First item is {{json.items.first}}.
Last item is {{json.items.last}}.

{% for s in json.items %}
=== {{forloop.index0}} -> {{forloop.index0 | plus: 1}} {{s}} == {{json.items[forloop.index0]}}

[source,ruby]
----
include::{{json.prefix}}{{forloop.index0}}.rb[]
----

{% endfor %}
------

Will render as:

First item is lorem.
Last item is dolor.

=== 0 -> 1 lorem == lorem

[source,ruby]
----
include::doc-0.rb[]
----

=== 1 -> 2 ipsum == ipsum

[source,ruby]
----
include::doc-1.rb[]
----

=== 2 -> 3 dolor == dolor

[source,ruby]
----
include::doc-2.rb[]
----