skip to Main Content

I’d like to default values in place on jq, and return root document.

What I have (note that some property is missing from the inner object):

[{"foo":{}},{"foo":{"a":1}},{"foo":{"b":4,"c":23}},{"foo":{"a":15,"b":2,"c":33}}]

What I want (default missing property with 0):

[
  {
    "foo": {
      "a": 0,
      "b": 0,
      "c": 0
    }
  },
  {
    "foo": {
      "a": 1,
      "b": 0,
      "c": 0,
    }
  },
  {
    "foo": {
      "a": 0,
      "b": 4,
      "c": 23
    }
  },
  {
    "foo": {
      "a": 15,
      "b": 2,
      "c": 33
    }
  }
]

I’ve tried something like jq '.[] | (.foo | getpath(["a"]) //= 0)' < in > out, but this returns stream of the inner object instead of root document; this is not what I wanted because there are other property to grouping record, I would like to group by it later.

2

Answers


  1. If fields clash on a merge, the right one overwrites the left one. You can utilize this fact by putting default values to the left, and merge with current values on the right. The kind of iteration over "current values" depends on your structure and your prerequisites.

    A merge, for example, may (*) or may not (+) be deep. Here are examples for both: The first one updates the .foo field by (shallow) merging it with an object just containing values for a, b, and c. The second one updates the root object by (deep) merging it with an object containing values for foo.a, foo.b, and foo.c. Both approaches output the original root level.

    map(.foo |= {a:0, b:0, c:0} + .)
    

    Demo

    map({foo: {a:0, b:0, c:0}} * .)
    

    Demo

    Alternatively, if you want a path-based approach, you could provide a list of paths to iterate over, and set values at those paths as needed. The following examples make use of the alternative operator // from your own approach (which does set the default value even if the original one indeed exists if it evaluates to null or false):

    map(reduce(
      ["foo", "a"],
      ["foo", "b"],
      ["foo", "c"]
    ) as $p (.; setpath($p; getpath($p) // 0)))
    

    Demo

    You may also provide and set the default values individually:

    map(reduce(
      [["foo", "a"], 0],
      [["foo", "b"], 0],
      [["foo", "c"], 0]
    ) as [$p, $v] (.; setpath($p; getpath($p) // $v)))
    

    Demo

    In any case, use map(...) (or .[] |= ..., or [.[] | ...]) instead of just .[] | ... in order to process the top-level array’s items and retain the array structure.

    Given your sample input, all of these approaches output the same result (with variations in field order for the latter two, path-based examples, as ↑here the default value in some cases is "introduced later" than the original ones, but they are, nevertheless, logically equivalent):

    [
      {
        "foo": {
          "a": 0,
          "b": 0,
          "c": 0
        }
      },
      {
        "foo": {
          "a": 1,
          "b": 0,
          "c": 0
        }
      },
      {
        "foo": {
          "a": 0,
          "b": 4,
          "c": 23
        }
      },
      {
        "foo": {
          "a": 15,
          "b": 2,
          "c": 33
        }
      }
    ]
    
    Login or Signup to reply.
  2. As I understand the problem, you want a simple way to generate the output shown in the question by setting the default values in just one place. Here’s one way:

    def foo(f): {foo: ({a:0, b:0, c:0} + f)};
    
    [
     foo(null),
     foo({a: 1}),
     foo({b: 4, c: 23}),
     foo({a:15,b:2,c:33})
     ]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search