'JSONata: flattening recursion that tracks path of recursion
I have a nested, recursive JSON of arbitrary depth where a node can either be a parent of more nodes (i.e. it has a structure
array containing nodes, which is the case for schema
or fieldGroup
nodes) or it can be a leaf (field
node).
I try to use JSONata to map it to a flattened structure. I have difficulty to track the recursion path - in this case I need to build a path of ids inside each recursively mapped field, but I can't.
Source JSON with three levels of nesting ("House no." is level 3):
{
"schema": {
"id": "S05000058",
"label": "Request to breed mice",
"structure": [
{
"contains": {
"field": {
"id": "F05001343",
"label": "Accept terms and conditions"
}
}
},
{
"contains": {
"field": {
"id": "F05001344",
"label": "Agree to share registration with ministry of mice"
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G05000496",
"label": "Requesting person",
"structure": [
{
"contains": {
"field": {
"id": "F05000059",
"label": "Last name"
}
}
},
{
"contains": {
"field": {
"id": "F05000060",
"label": "First name"
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G05000428",
"label": "Street address",
"structure": [
{
"contains": {
"field": {
"id": "F00000053",
"label": "Street"
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G05000429",
"label": "House no",
"structure": [
{
"contains": {
"field": {
"id": "F00000016",
"label": "Number"
}
}
},
{
"contains": {
"field": {
"id": "F00000016",
"label": "Appendix"
}
}
}
]
}
}
}
]
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G00000113",
"label": "Location",
"structure": [
{
"contains": {
"field": {
"id": "F00000054",
"label": "Postal code"
}
}
},
{
"contains": {
"field": {
"id": "F00000035",
"label": "City"
}
}
}
]
}
}
},
{
"contains": {
"field": {
"id": "F05000523",
"label": "Country"
}
}
}
]
}
}
}
]
}
}
My JSONata mapping uses the descendants path operator **
to flatten fieldGroups recursively, starting from the second nesting level.
https://try.jsonata.org/lfQdZcAe-
My problem is that I do not know how to get access to the ids of the parent nodes during **
recursion, therefore my $mapFields()
function is unable to build an idPath
for the recursion (last $mapFields()
invocation below):
(
$mapFields := function($datenfelder, $prefix) {
$datenfelder.{
"fields": [
{
"label": label,
"idPath": $prefix & '.' & id
}
]
}
};
{
"idPath": schema.id,
"sections": $append(
/* top-level fields go into general section */
{
"title": "Top level fields",
"fieldGroups": [
{
"title": "",
"rows": $mapFields(schema.structure.contains.field, schema.id)
}
]
},
/* subsequent sections contain top-level field groups */
schema.structure.contains.fieldGroup.{
"title": label,
"idPath": $$.schema.id & '.' & id,
"fieldGroups": [
$append(
{
"title": "",
"rows": $mapFields(structure.contains.field, $$.schema.id & '.' & id)
},
/* create fieldGroups recursively */
structure.contains.**.fieldGroup.{
"title": label,
/* how to build group idPath from third level: */
"idPath": $$.schema.id & '.' & id,
/* how to pass recursive path prefix here: */
"rows": $mapFields($.structure.contains.field, ???)
}
)
]
}
)
})
My JSONata fails to produce complete idPaths starting from the fieldGroup "Street address" (which is on the second level):
{
"idPath": "S05000058",
"sections": [
{
"title": "Top level fields",
"fieldGroups": [
{
"title": "",
"rows": [
{
"fields": [
{
"label": "Accept terms and conditions",
"idPath": "S05000058.F05001343"
}
]
},
{
"fields": [
{
"label": "Agree to share registration with ministry of mice",
"idPath": "S05000058.F05001344"
}
]
}
]
}
]
},
{
"title": "Requesting person",
"idPath": "S05000058.G05000496",
"fieldGroups": [
{
"title": "",
"rows": [
{
"fields": [
{
"label": "Last name",
"idPath": "S05000058.G05000496.F05000059"
}
]
},
{
"fields": [
{
"label": "First name",
"idPath": "S05000058.G05000496.F05000060"
}
]
},
{
"fields": [
{
"label": "Country",
"idPath": "S05000058.G05000496.F05000523"
}
]
}
]
},
{
"title": "Street address",
"idPath": "S05000058.G05000428",
"rows": {
"fields": [
{
"label": "Street",
"idPath": ".F00000053" /* incomplete */
}
]
}
},
{
"title": "House no",
/* incomplete, should be "S05000058.G05000428.G05000429": */
"idPath": "S05000058.G05000429",
"rows": [
{
"fields": [
{
"label": "Number",
"idPath": ".F00000016" /* incomplete */
}
]
},
{
"fields": [
{
"label": "Appendix",
"idPath": ".F00000016" /* incomplete */
}
]
}
]
},
{
"title": "Location",
"idPath": "S05000058.G00000113",
"rows": [
{
"fields": [
{
"label": "Postal code",
"idPath": ".F00000054" /* incomplete */
}
]
},
{
"fields": [
{
"label": "City",
"idPath": ".F00000035" /* incomplete */
}
]
}
]
}
]
}
]
}
Note that it is not sufficient to pass $$.schema.id & '.' & id
to the recursive $mapFields()
call, as shown by the "House No" fieldGroup on the third level - all fields starting from the third level would get a fixed prefix which does not reflect the actual depth of recursion.
I have also tried to create a recursion manually as in How to flatten nested object to single depth object with JSONata?, but I didn't manage to iterate over the structure
array during recursion.
Solution 1:[1]
I believe the "parent" path operator is what you want here: https://stedi.link/NR2Ax3Z
You can check how the parent operator works in the docs: https://docs.jsonata.org/path-operators#-parent
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Alexander Sennikov |