Skip to content

Json-comprehension

Mpl supports sophisticated JSON comprehension, which can walk a document and pick any key/val from the JSON path. We introduced the JSON doc create capability earlier where variables are used in place of JSON constants. e.g.,

v = 1
k = "key"
json {k:v}

output is:

{"key":1}

One may want to filter and create another document conditionally in a complex JSON document. In general, such operations require nested for loops to pick required values. MPL's promise is not to require the use of 'for' loops, in general. MPL supports an advanced and intuitive way to choose desired data from doc and create another one. We saw the mutation feature before, which allows picking fields from multiple sub-document by simple range operators on an array or object. e.g., x[1:3] or y[*], As we mutate a document, a local variable is created for each subtree. In JSON comprehension, one can use this local variable (prefixed with @) to create documents with matching paths. The syntax for JSON comprehension is json{<json_expr>} or json[<json_expr>], where json_expr is expression similar as in json statement described earlier. e.g.,

x = json [1,5]
r = "d"
s = 1
x[].json{r:s}

output is:

[{"d":1},{"d":1}]

Here .json is a built-in that creates a JSON map or array. Note that the expression {r:s} is applied to mutated JSON array x, MPL evaluates the expression for each element in x. x[] results in a JSON list 1 5, and for each member of JSON list MPL evaluates the .json expression {r:s}.

In the above example, we do not use any local variable. For each mutation level, MPL creates a local variable prefixed by @, e..g, @0, @1, and so on. Since we have only one mutation step in the above example, only @0 is created. This variable can be used while doing JSON comprehension using the builtin .json

x = json [2,5]
k = "d"
x[].json{k:x[@0]}

output is:

[{"d":2},{"d":5}]

Note that @0 resolves to array index of x. Hence the expression x[@0] resolves to 2 and 5 when applied to mutated JSON list created at previous stage, x[].

Following example is on two level map:

x = json {"g1":{"a":1, "b":2}, "g2":{"a1":11, "b1":12}}
x[*][*].json{@0:x[@0][@1]}

output is:

[{"g1":1},{"g1":2},{"g2":11},{"g2":12}]

Here @0 gets assigned the first level keys g1 and g2. @1 is a set of second-level keys a, b, a1 and a2. Both @0 and @1 are used to access values in JSON doc, x.

In the following document, we have data of great-grandfathers and grandfathers. We want to pick the data of ancestors of grandfathers whose net worth is > 70. Doc is as below:

x = json {"ggf": [ { "ggf-1": { "age": 42, "worth": 200,
                        "gf": [
                            { "gf11": { "age": 32, "worth": 90 } },
                            { "gf12": { "age": 75, "worth": 70 } }
                        ]
                    } },
                { "ggf-2": { "age": 52, "worth": 100,
                        "gf": [
                            { "gf21": { "age": 22, "worth": 60 } },
                            { "gf22": { "age": 65, "worth": 80 } }
                        ]
                    } }
            ]
        }
x.ggf[][*].gf[][*][?.worth>70].json{"ggf":@1, "gf":@3, "age":.age}

output is:

[{"ggf":"ggf-1","gf":"gf11","age":32},{"ggf":"ggf-2","gf":"gf22","age":65}]

Note that the 'age' field is derived simply by .age relative ID as its the leaf node. The same can also be accessed via absolute variable x.ggf[@0][@1].gf[@2][@3].age. Here we use the 'filter' feature to collect the subdocument (where worth > 70) and then apply the picked JSON paths to json expression {"ggf":@1, "gf":@3, "age":.age}.

As demonstrated, JSON comprehension is a powerful and intuitive feature to create a JSON document. One can use this capability to join multiple docs as well. Imagine we have a record where grandfather's fav color is available. We can use the same above expression to pick fields from this new document. e.g.,

y = json {"gf21": "red", "gf11": "blue", "gf12": "green", "gf22": "pink"}
x.ggf[][*].gf[][*][?.worth>70].json{"ggf":@1, "gf":@3, "age":.age, "color": y[@3]}

output is:

[{"ggf":"ggf-1","gf":"gf11","age":32,"color":"blue"},{"ggf":"ggf-2","gf":"gf22","age":65,"color":"pink"}]

Above the variable @3 is used to access another JSON doc y.

Named local variable:

So far, we saw that MPL creates the local @ variables automatically. Instead of using @0, @1, one can name these variables by using @<var>= syntax in subscript e.g., [@u=] or [@v=*]. We can write the above example as:

x = json {"ggf": [ { "ggf-1": { "age": 42, "worth": 200,
                        "gf": [
                            { "gf11": { "age": 32, "worth": 90 } },
                            { "gf12": { "age": 75, "worth": 70 } }
                        ]
                    } },
                { "ggf-2": { "age": 52, "worth": 100,
                        "gf": [
                            { "gf21": { "age": 22, "worth": 60 } },
                            { "gf22": { "age": 65, "worth": 80 } }
                        ]
                    } }
            ]
        }
x.ggf[@r1=][@r2=*].gf[][*][?.worth>70].json{"ggf":@r2, "gf":@1, "age":x.ggf[@r1][@r2].gf[@0][@1].age}

Note that @0 and @1 represents @2 and @3 values. The first 2 mutations are named as r1 and r2 respectively. Output is the same:

[{"ggf":"ggf-1","gf":"gf11","age":32},{"ggf":"ggf-2","gf":"gf22","age":65}]