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:
-
{variable}
: as in AsciiDoc syntax; -
{{ 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 ofvariable
. -
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.
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}}
.
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 itemitem_name
within the parent array -
{{forloop.length}}
returns the number of iterations of the loop. -
{{forloop.first}}
returnstrue
if it’s the first iteration of the for loop. Returnsfalse
if it is not the first iteration. -
{{forloop.last}}
returnstrue
if it’s the last iteration of the for loop. Returnsfalse
if it is not the last iteration. -
{{array_name.size}}
gives the length of the arrayarray_name
-
{{array_name[i]}}
provides the value at indexi
(zero-based: starts with0
) in the arrayarray_name
;-1
can be used to refer to the last item,-2
the second last item, and so on.
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 itemitem
in the parent arrayarr
.
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}}
.
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
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.
keys
and values
in object enumerationGiven:
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 arraymy_item
. -
{{item.values[1]}}
gives the value located at the second key withinitem
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.
limit
and offset
attributes in a for loopGiven:
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
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
, andnum
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.
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[]
----