9

I'm adding strings to a JSON array using jq, and it works great, but I'd like to only add strings that do not already exist. I've experimented with unique, has, not, etc. I'm missing a piece or two to the puzzle.

Here's my starting json file, foo.json:

{
  "widgets": [
    {
      "name": "foo",
      "properties": [
        "baz"
      ]
    },
    {
      "name": "bar"
    }
  ]
}

Here's the jq command I've constructed that adds the string, even if it already exists:

$ cat foo.json | jq '.widgets[] | select(.name=="foo").properties |= .+ ["cat"]'

Here's the latest iteration of my attempt.

$ cat foo.json | jq '.widgets[] | select(.name=="foo").properties | has("cat") | not | .properties += ["cat"]'
jq: error: Cannot check whether array has a string key
2
  • NOTE: I need to print the entire file, my iteriation doesn't account for that. Commented Aug 23, 2015 at 17:26
  • I solved it with this: jq '.widgets[] |= (select(.name=="foo").properties |= (.+ ["cat"] | unique))' Commented Aug 23, 2015 at 17:36

2 Answers 2

10

[THIS IS NOT WORKING. READ BELOW.]

There are many ways to do this.

Assuming elements of the array are supposed to be unique, which your use case strongly implies, you can just pass the resulting array after the addition through the unique filter.

$ cat foo.json | jq '.widgets[] | select(.name=="foo").properties |= (.+ ["cat"] | unique)'

There are a few problems here.

One is that the resulting output is partial as it is missing the container object.

Another one is that the edited array looses the commas separating the objects thus becoming illegal JSON.

The actual result from the above command is:

{
  "name": "foo",
  "properties": [
    "baz",
    "cat"
  ]
}
{
  "name": "bar"
}
Sign up to request clarification or add additional context in comments.

6 Comments

Brilliant, this is exactly what I was looking for. I misunderstood the use of unique but it falls into place now.
I realize now that this approach makes widgets[] the root element. What is the best approach to get around that?
Santiago, when adding code, always indent with 4 spaces, don't use backticks. Only use those for highlighting keywords and other inline things.
@JeffMercado Noted! Thanks for the feedback :)
@brian Wrapping the left hand side of the |= in parenthesis should do it.
|
3

There's more than one way to skin a cat, as they say, but perhaps this will give you some ideas:

.widgets[]
| select(.name=="foo")
| select(.properties | index("cat") | not)
| .properties += ["cat"]

With your input, the result is:

{
  "name": "foo",
  "properties": [
    "baz",
    "cat"
  ]
}

The following may be closer to what you're looking for:

.widgets |= [ .[] | if .properties|index("cat")|not
                    then .properties += ["cat"]
                    else .
                    end]

3 Comments

Hmm.. interesting! I tested it and I agree it works as you quoted, but if cat already exists it prints nothing. It only prints the foo widget, but I think I can fix that by something such as '(expr) | .[0]'.
Your second example is helpful - but it does not filter by name.
I think I got it solved - thank you. You gave me the idea that helped me figure it out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.