skip to Main Content

For some reason I just can’t wrap my head around jq. I’ve been going around in circles for hours trying to accomplish what I think is not too difficult a task.
With json consisting of two arrays as input, I want to search for objects in the second array whereby the name field value is an element of the first array, and then add a field, say "found": true, to those objects.

[
    [
        "test1",
        "test2",
        "test3"
    ],
    [
        {
            "name": "test5",
            "data": "five"
        },
        {
            "name": "test1",
            "data": "four"
        },
        {
            "name": "test2",
            "data": "three"
        },
        {
            "name": "test3",
            "data": "six"
        }
    ]
]

My best guess so far starts with…
.[0] as $tn | (.[1] | .[] | .name | in($tn[]))
but that is not getting me close to where i want to go.

2

Answers


  1. Your attempt has only two issues: First, you need select to filter out certain items. Second, there are two (confusingly named) functions: The one called in tests for the existence of keys in objects, the other one called IN searches for occurrence in a stream or array. You need the latter.

    .[0] as $tn | .[1][] | select(.name | IN($tn[])) | .found = true
    
    {
      "name": "test1",
      "data": "four",
      "found": true
    }
    {
      "name": "test2",
      "data": "three",
      "found": true
    }
    {
      "name": "test3",
      "data": "six",
      "found": true
    }
    

    Demo

    If, however, you wanted to retain the original structure, you need to retain the top-level context until the end of your filter, while manipulating parts of it using updates |= as in:

    .[0] as $tn | (.[1][] | select(.name | IN($tn[]))).found = true
    
    [
      [
        "test1",
        "test2",
        "test3"
      ],
      [
        {
          "name": "test5",
          "data": "five"
        },
        {
          "name": "test1",
          "data": "four",
          "found": true
        },
        {
          "name": "test2",
          "data": "three",
          "found": true
        },
        {
          "name": "test3",
          "data": "six",
          "found": true
        }
      ]
    ]
    

    Demo

    And to set the boolean flag on all items, just assign, don’t select:

    .[0] as $tn | .[1][] |= (.found = (.name | IN($tn[])))
    
    [
      [
        "test1",
        "test2",
        "test3"
      ],
      [
        {
          "name": "test5",
          "data": "five",
          "found": false
        },
        {
          "name": "test1",
          "data": "four",
          "found": true
        },
        {
          "name": "test2",
          "data": "three",
          "found": true
        },
        {
          "name": "test3",
          "data": "six",
          "found": true
        }
      ]
    ]
    

    Demo

    Login or Signup to reply.
  2. Close enough. Use IN to check for items in a stream and then assign the new property:

    first as $names
    | .[1][] | select(.name|IN($names[]))
    | .found = true
    

    Output:

    {
      "name": "test1",
      "data": "four",
      "found": true
    }
    {
      "name": "test2",
      "data": "three",
      "found": true
    }
    {
      "name": "test3",
      "data": "six",
      "found": true
    }
    

    If you want to keep the input structure and only add found to the matching elements in the nested array, use parentheses:

    first as $names
    | (.[1][] | select(.name|IN($names[]))).found = true
    

    Output:

    [
      [
        "test1",
        "test2",
        "test3"
      ],
      [
        {
          "name": "test5",
          "data": "five"
        },
        {
          "name": "test1",
          "data": "four",
          "found": true
        },
        {
          "name": "test2",
          "data": "three",
          "found": true
        },
        {
          "name": "test3",
          "data": "six",
          "found": true
        }
      ]
    ]
    

    And finally to transform and extract only the second array (as array):

    first as $names
    | (.[1] | map(select(.name|IN($names[])).found = true))
    

    Output:

    [
      {
        "name": "test5",
        "data": "five"
      },
      {
        "name": "test1",
        "data": "four",
        "found": true
      },
      {
        "name": "test2",
        "data": "three",
        "found": true
      },
      {
        "name": "test3",
        "data": "six",
        "found": true
      }
    ]
    

    To keep only the matching elements (note the | before assignment of .found):

    first as $names
    | (.[1] | map(select(.name|IN($names[])) | .found = true))
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search