veman
veman

Reputation: 73

How to transform json tree recursively with jq?

I have the following JSON that I need to transform with jq. The main task is to leave only specified attributes in the tree. The number of levels in the tree may be different.

    {
    "metaModel": [ {
        "clazz": "AttributeClass1",
        "code": "code1",
        "default": "value1",
        "children": [ {
            "clazz": "AttributeClass2",
            "code": "code21",
            "default": "value21"
        },
        {
            "clazz": "AttributeClass1",
            "code": "code22",
            "default": "value22",
            "children": [ {
                "clazz": "AttributeClass1",
                "code": "code31",
                "default": "value31",
                "children": []
            }
            ]
        }
        ]
    },
    {
        "clazz": "AttributeClass2",
        "code": "code2",
        "default": "value2"
    }
    ]
}

Is it possible to get the output like the following (leave only clazz, code, children)?

    {
        "clazz": "AttributeClass1",
        "code": "code1",
        "children": [ {
            "clazz": "AttributeClass2",
            "code": "code21"
        },
        {
            "clazz": "AttributeClass1",
            "code": "code22",
            "children": [ {
                "clazz": "AttributeClass1",
                "code": "code31",
                "children": []
            }
            ]
        }
        ]
    },
    {
        "clazz": "AttributeClass2",
        "code": "code2"
    }

'metaModel' may be left in the output too.

Upvotes: 0

Views: 1024

Answers (3)

peak
peak

Reputation: 116750

Using walk/1:

.metaModel
| walk( if type == "object" and has("clazz")
        then {clazz, code} + (if .children then { children } else null end )
        else .
        end )

Upvotes: 1

jq170727
jq170727

Reputation: 14665

Here is an approach that uses tostream, setpath and reduce:

reduce (tostream|select(.[0][-1]|.=="clazz" or .=="code" or .=="children")) as [$p,$v] (
  {}; setpath($p;$v)
)

Try it online!

tostream converts your input to "streaming" [path,value] form:

[["metaModel",0,"children",0,"clazz"],"AttributeClass2"]
[["metaModel",0,"children",0,"code"],"code21"]
[["metaModel",0,"children",0,"default"],"value21"]
[["metaModel",0,"children",0,"default"]]
[["metaModel",0,"children",1,"children",0,"children"],[]]
[["metaModel",0,"children",1,"children",0,"clazz"],"AttributeClass1"]
[["metaModel",0,"children",1,"children",0,"code"],"code31"]
[["metaModel",0,"children",1,"children",0,"default"],"value31"]
[["metaModel",0,"children",1,"children",0,"default"]]
[["metaModel",0,"children",1,"children",0]]
[["metaModel",0,"children",1,"clazz"],"AttributeClass1"]
[["metaModel",0,"children",1,"code"],"code22"]
[["metaModel",0,"children",1,"default"],"value22"]
[["metaModel",0,"children",1,"default"]]
[["metaModel",0,"children",1]]
[["metaModel",0,"clazz"],"AttributeClass1"]
[["metaModel",0,"code"],"code1"]
[["metaModel",0,"default"],"value1"]
[["metaModel",0,"default"]]
[["metaModel",1,"clazz"],"AttributeClass2"]
[["metaModel",1,"code"],"code2"]
[["metaModel",1,"default"],"value2"]
[["metaModel",1,"default"]]
[["metaModel",1]]
[["metaModel"]]

select filters out all paths not ending in "clazz", "code" or "children":

[["metaModel",0,"children",0,"clazz"],"AttributeClass2"]
[["metaModel",0,"children",0,"code"],"code21"]
[["metaModel",0,"children",1,"children",0,"children"],[]]
[["metaModel",0,"children",1,"children",0,"clazz"],"AttributeClass1"]
[["metaModel",0,"children",1,"children",0,"code"],"code31"]
[["metaModel",0,"children",1,"clazz"],"AttributeClass1"]
[["metaModel",0,"children",1,"code"],"code22"]
[["metaModel",0,"clazz"],"AttributeClass1"]
[["metaModel",0,"code"],"code1"]
[["metaModel",1,"clazz"],"AttributeClass2"]
[["metaModel",1,"code"],"code2"]

reduce and setpath reconstruct the final result:

{
  "metaModel": [
    {
      "children": [
        {
          "clazz": "AttributeClass2",
          "code": "code21"
        },
        {
          "children": [
            {
              "children": [],
              "clazz": "AttributeClass1",
              "code": "code31"
            }
          ],
          "clazz": "AttributeClass1",
          "code": "code22"
        }
      ],
      "clazz": "AttributeClass1",
      "code": "code1"
    },
    {
      "clazz": "AttributeClass2",
      "code": "code2"
    }
  ]
}

Upvotes: 0

peak
peak

Reputation: 116750

The following recursive function seems to meet the requirements, or at least the filter .metaModel | transform transforms the sample input in accordance with the sample output:

def transform:
  if type == "object" and has("clazz")
  then  {clazz, code} + (if has("children") then {children: (.children|transform)} else null end)
  elif type == "array" then map(transform)
  else .
  end;

Footnote

If you don't like the redundancy of the expression

{children: (.children|transform)}

you could write:

{children} | map_values(transform)

Upvotes: 1

Related Questions