Home > Blockchain >  Using jq to conditionally flatten recursive json
Using jq to conditionally flatten recursive json

Time:10-02

The simplest version of the input document I could come up with is

{
    "references": [
        {
            "version": 5,
            "id": "id1",
            "objType": "A"
        },
        {
            "version": 4,
            "id": "id2",
            "objType": "B",
            "referencing": []
        },
        {
            "version": 4,
            "id": "id3",
            "objType": "B",
            "referencing": [
                {
                    "version": 2,
                    "id": "id4",
                    "objType": "A"
                },
                {
                    "version": 3,
                    "id": "id5",
                    "objType": "B",
                    "referencing": []
                }
            ]
        }
    ]
}

Objects of type A have no referencing objects.

Objects of type B can be referenced by either type of object.

There are two outputs I need from this json:

Output #1 is the version info for objects of type A with the id value as a key with the value of version. A objects can be at the top level or at some arbitrary depth in the referencing arrays.

{
    "references": {
        "id1": {"version": 5},
        "id4": {"version": 2}
    }
}

The 2nd output is similar: the version info for objects of type B. The can be a chain of type B objects referencing other type B objects.

{
    "references": {
        "id2": {"version": 4},
        "id3": {"version": 4},
        "id5": {"version": 3}
    }
} 

CodePudding user response:

Use recursive decsent operator and from_entries. You don't need to follow the "references" (at least not to produce the expected output in your question)

{
    dependencies: [.. | select(.objType=="A")? | { key: .id, value: {version} }] | from_entries
},
{
    dependencies: [.. | select(.objType=="B")? | { key: .id, value: {version} }] | from_entries
}

Output:

{
  "dependencies": {
    "id1": {
      "version": 5
    },
    "id4": {
      "version": 2
    }
  }
}
{
  "dependencies": {
    "id2": {
      "version": 4
    },
    "id3": {
      "version": 4
    },
    "id5": {
      "version": 3
    }
  }
}

It's also possible to merge (add) objects instead of constructing them from their entries, which makes the code minimally shorter:

{
    dependencies: [.. | select(.objType=="A")? | { (.id): {version} }] | add
}

CodePudding user response:

You can use recurse to traverse the document, INDEX to create an object with IDs as keys, map_values to format their values using select to reduce according to your criteria.

jq --arg type A '
  .references |= (
    INDEX(.[] | recurse(.referencing[]?); .id)
    | map_values(select(.objType == $type) | {version})
  )
'
{
  "references": {
    "id1": {
      "version": 5
    },
    "id4": {
      "version": 2
    }
  }
}

Demo

This works for both questions, provide A or B to --arg type.

Note that this is using the error suppression operator ? when recursing down. If you want to restrict the traversal explicitly to .objType == "B", just prepend it in a select expression, i.e. replace recurse(.referencing[]?) with recurse(select(.objType == "B") | .referencing[]). Demo

  • Related