jsonjq

How to Subtract Objects in jq?


We can merge two objects recursively with * operator. For example like this: jq '.[0] * .[1]' -s a.json b.json. The only problem (I realized) with it is it doesn't merge the arrays. But some guys did it.


What I want is the opposite. Assuming the things in b.json are always exist in a.json; a.json, b.json and the result respectively:

{
  "background": {
    "service_worker": "js/background.js",
    "type": "module"
  },
  "manifest_version": 3,
  "permissions": [
    "storage",
    "unlimitedStorage"
  ]
}
{
  "background": {
    "type": "module"
  },
  "permissions": [
    "unlimitedStorage"
  ]
}
{
  "background": {
    "service_worker": "js/background.js"
  },
  "manifest_version": 3,
  "permissions": [
    "storage"
  ]
}

There are some questions on Stack Overflow like this but they are specific cases. I rather have a generic one like the meld function.


I tried to edit the meld function but couldn't achieve anything worth to mention.


Solution

  • Using the following definitions, $a | subtract_json($b) produces the desired result, where $a and $b represent the values in a.json and b.json respectively:

    def relevant_paths:
      paths
      | select( .[-1] | type != "number")  ;
      
    def is_scalar: type | IN("object", "array") | not;
    
    def subtract_json($y):
      . as $x
      | reduce ($y | relevant_paths) as $p ( $x;
          if (getpath($p)|type == "array") and
             ($y|getpath($p)|type == "array")
          then setpath($p;  getpath($p) - ($y|getpath($p)) )
          elif (getpath($p)|is_scalar) and 
               ($y|getpath($p)|is_scalar)
          then delpaths( [$p] )
          end);