Use jq to Format Certain Fields as Compact?
Asked Answered
K

1

8

Is jq the best choice for pretty-printing arbitrary JSON?

cat my.json | jq . pretty-prints the given JSON, but expands every field on a separate line.

But what if some of the fields are repetitive, such as a list of points? How can fields that match a pattern be formatted on a single line with --compact-output?

For example, format "coords" and "list" fields below on a single line:

 [
   { 
      "field1": {
        "a": "",
        "b": ""
        "list": [{ "name": "x", "score": 1, "rect": { "x": 156, "y": 245, "w": 35, "h": 45 }, ... ]
      },
      "field2": 2,
      "coords": [{ "x": 100, "y": 400 },{ "x": 100, "y": 0 }]
    },
    ....
 ]

The fields formatted with --compact-output can wrap (no need to break these long lines).

Kristlekristo answered 11/3, 2017 at 0:4 Comment(2)
I don't know of a solution, but it is a known issue. github.com/stedolan/jq/issues/643Quarrelsome
I suppose it would be possible to write a filter to manually format certain objects if it were streamed in. Fields of interest would be output to a buffer to be compacted and others would be dumped to the output. Then when the end of the field is reached, the buffer is output. As a stream, you get the start and stop of the json objects and arrays along with its path. Then of course it would have to be output raw.Exorbitant
S
3

A constrained-width JSON pretty-printer could be written in jq itself. Here is a pretty-printer which illustrates how this could be done, though in its present incarnation, it is of limited usefulness.

ppArray(indent; incr; width) will emit a stream of JSON strings that together are equivalent to the tostring value of the input. For robustness, it will always act as a pretty-printer, even if the width restriction is violated.

If the input is an array, and if none of its elements (or recursively their elements) contains any large objects or long strings, then assuming the values of the parameters are reasonably chosen and that nesting is not too deep relative to these parameters, each emitted string should be no longer than "width".

# indent is the initial indentation level;
# incr is the number of spaces to add for one additional indentation level;
# width is the target maximum width.
#
def ppArray(indent; incr; width):
  # The inner function produces an array of unindented strings.
  def ppArray_(incr_; width_):
    tostring as $tostring
    | if $tostring|length <= (width_ - incr_) then [$tostring]
      else reduce .[] as $i
        ([];  
         ($i|tostring) as $is
          | if length == 0 then [ $is ]
            else .[-1] as $s
            | ($s|length) as $n
            | ($is|length) as $isl
            | if $n + $isl <= (width_ - incr_)
             then .[-1] = ($s + ", " + $is)
              elif ($i|type) == "array"
             then (.[-1]+=",") + [ $i | ppArray(0; incr_; width_ - incr_) ]
              else  (.[-1]+=",") + [ $is ]
              end 
            end )
      end;

    (" " * indent) as $indentation
    | if type == "array" 
      then ppArray_(incr; width - indent)
           | $indentation + "[",
           (.[] | ($indentation + "  " + . )),
           $indentation + "]"
    else $indentation + tostring
    end
;

Example:

[range(0;16)]
|
(ppArray(0; 2; 10)),
"::",
([{a:1}, {b:2}, {c:3}]  | ppArray(0; 2; 10)),
"::",
(.[2]=[range(0;10)]) | ppArray(0; 2; 10)

Invocation:

jq -nrf pp.jq

Output:

[
  0, 1, 2, 3,
  4, 5, 6, 7,
  8, 9, 10,
  11, 12, 13,
  14, 15
]
::
[
  {"a":1},
  {"b":2},
  {"c":3}
]
::
[
  0, 1,
  [
    0, 1, 2,
    3, 4, 5,
    6, 7, 8,
    9
  ], 3, 4, 5,
  6, 7, 8, 9,
  10, 11, 12,
  13, 14, 15
]
Sukhum answered 11/3, 2017 at 3:17 Comment(2)
Excellent idea! More complicated than expected, but innovative. What I'm really looking for is a way to place certain fields on a single line, while all others go on a separate line. Would that be simpler to solve in jq?Kristlekristo
For a practical approach combining jq and a post-processing step, see #46806333Sukhum

© 2022 - 2024 — McMap. All rights reserved.