Format certain JSON objects on one line
<p>Consider the following code:</p>

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

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

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

<p>I have played around with implementing a custom <a href="https://docs.python.org/3/library/json.html#json.JSONEncoder" rel="nofollow"><code>JSONEncoder</code></a> and passing that in to <code>json.dumps</code> as the <code>cls</code> argument, but the two methods on <code>JSONEncoder</code> each have a problem:</p>

<ul>
<li><p>The <code>JSONEncoder</code> <a href="https://docs.python.org/3/library/json.html#json.JSONEncoder.default" rel="nofollow"><code>default</code></a> method is called for each part of <code>data</code>, but the return value is not a raw JSON string, so there doesn't appear to be any way to adjust its formatting.</p></li>
<li><p>The <code>JSONEncoder</code> <a href="https://docs.python.org/3/library/json.html#json.JSONEncoder.encode" rel="nofollow"><code>encode</code></a> method does return a raw JSON string, but it is only called once for the <code>data</code> as a whole.</p></li>
</ul>

<p>Is there any way I can get <code>JSONEncoder</code> to do what I want?</p>

<p>You can do it, but you'd basically have to copy/modify a lot of the code out of <code>json.encoder</code> because the encoding functions aren't really designed to be partially overridden.</p>

<p>Basically, copy the entirety of <code>_make_iterencode</code> from <code>json.encoder</code> and make the changes so that your special dictionary gets printed without newline indents.  Then monkeypatch the json package to use your modified version, run the json dump, then undo the monkeypatch (if you want).</p>

<p>The <code>_make_iterencode</code> function is pretty long, so I've only posted the portions that need to be changed.</p>

<pre><code>import json
import json.encoder

def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
    ...
    def _iterencode_dict(dct, _current_indent_level):
        ...
        if _indent is not None:
            _current_indent_level += 1
            if '$special' in dct:
                newline_indent = ''
                item_separator = _item_separator
            else:
                newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
                item_separator = _item_separator + newline_indent
            yield newline_indent
        ...
        if newline_indent is not None:
            _current_indent_level -= 1
            if '$special' not in dct:
                yield '\n' + (' ' * (_indent * _current_indent_level))

def main():
    data = {
        'x': [1, {'$special': 'a'}, 2],
        'y': {'$special': 'b'},
        'z': {'p': True, 'q': False},
    }

    orig_make_iterencoder = json.encoder._make_iterencode
    json.encoder._make_iterencode = _make_iterencode
    print(json.dumps(data, indent=2))
    json.encoder._make_iterencode = orig_make_iterencoder
</code></pre>

python json python-3.x formatting