jq

How to exclude a particular subtree from processing descendants in jq


I have the following json input:

{
  "to_exclude": {
    "x": "something",
    "aa": {
      "x": "something",
      "aaa": {
        "x": "something"
      }
    }
  },
  "a": {
    "x": "something else",
    "aa": {
      "x": "something else",
      "aaa": {
        "x": "something else"
      }
    }
  },
  "b": {
    "x": "something else",
    "bb": {
      "x": "something else",
      "bbb": {
        "x": "something else"
      }
    }
  },
  "...": {
    "x": "something else",
    "......": {
      "x": "something else",
      ".........": {
        "x": "something else"
      }
    }
  }
}

I'd like to replace the values of all x keys that are not descendants of to_exclude by anything.

The output should look like this:

{
  "to_exclude": {
    "x": "something",
    "aa": {
      "x": "something",
      "aaa": {
        "x": "something"
      }
    }
  },
  "a": {
    "x": "anything",
    "aa": {
      "x": "anything",
      "aaa": {
        "x": "anything"
      }
    }
  },
  "b": {
    "x": "anything",
    "bb": {
      "x": "anything",
      "bbb": {
        "x": "anything"
      }
    }
  },
  "...": {
    "x": "anything",
    "......": {
      "x": "anything",
      ".........": {
        "x": "anything"
      }
    }
  }
}

How can I achieve this in a similarily compact way as I could easily achieve the opposite? (the replacement for all values of x keys that are descendants of to_exclude):

.to_exclude |= (.. |= ( if type == "object" and has("x") then .x = "anything" else . end))

Output:

{
  "to_exclude": {
    "x": "anything",
    "aa": {
      "x": "anything",
      "aaa": {
        "x": "anything"
      }
    }
  },
  "a": {
    "x": "something else",
    "aa": {
      "x": "something else",
      "aaa": {
        "x": "something else"
      }
    }
  },
  "b": {
    "x": "something else",
    "bb": {
      "x": "something else",
      "bbb": {
        "x": "something else"
      }
    }
  },
  "...": {
    "x": "something else",
    "......": {
      "x": "something else",
      ".........": {
        "x": "something else"
      }
    }
  }
}

Solution

  • Instead of applying a function to .to_exclude using .to_exclude |= …, apply it to all keys after filtering them using a simple select. For example:

    .[keys[] | select(. != "to_exclude")] |= …
    

    Demo

    To exclude a list of keys, you could also use array subtraction:

    .[keys - ["to_exclude"] | .[]] |= …
    

    Demo