Timothy Shields Timothy Shields - 1 month ago 7
JSON Question

Format certain JSON objects on one line

Consider the following code:

>>> import json
>>> data = {
... 'x': [1, {'$special': 'a'}, 2],
... 'y': {'$special': 'b'},
... 'z': {'p': True, 'q': False}
... }
>>> print(json.dumps(data, indent=2))
{
"y": {
"$special": "b"
},
"z": {
"q": false,
"p": true
},
"x": [
1,
{
"$special": "a"
},
2
]
}


What I want is to format the JSON so that JSON objects that have only a single property
'$special'
are rendered on a single line, as follows.

{
"y": {"$special": "b"},
"z": {
"q": false,
"p": true
},
"x": [
1,
{"$special": "a"},
2
]
}


I have played around with implementing a custom
JSONEncoder
and passing that in to
json.dumps
as the
cls
argument, but the two methods on
JSONEncoder
each have a problem:


  • The
    JSONEncoder
    default
    method is called for each part of
    data
    , but the return value is not a raw JSON string, so there doesn't appear to be any way to adjust its formatting.

  • The
    JSONEncoder
    encode
    method does return a raw JSON string, but it is only called once for the
    data
    as a whole.



Is there any way I can get
JSONEncoder
to do what I want?

Answer

I found the following regex-based solution to be simplest, albeit … regex-based.

import json
import re
data = {
    'x': [1, {'$special': 'a'}, 2],
    'y': {'$special': 'b'},
    'z': {'p': True, 'q': False}
}
text = json.dumps(data, indent=2)
pattern = re.compile(r"""
{
\s*
"\$special"
\s*
:
\s*
"
((?:[^"]|\\"))*  # Captures zero or more NotQuote or EscapedQuote
"
\s*
}
""", re.VERBOSE)
print(pattern.sub(r'{"$special": "\1"}', text))

The output follows.

{
  "x": [
    1,
    {"$special": "a"},
    2
  ],
  "y": {"$special": "b"},
  "z": {
    "q": false,
    "p": true
  }
}