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:

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:

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:

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:

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:

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:

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:

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:

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:

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[]
----