<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dan Lumatic</title>
    <description>The latest articles on DEV Community by Dan Lumatic (@dan_lumatic).</description>
    <link>https://dev.to/dan_lumatic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4186289%2F9e101898-a70f-457f-82d0-b79095daef95.png</url>
      <title>DEV Community: Dan Lumatic</title>
      <link>https://dev.to/dan_lumatic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dan_lumatic"/>
    <language>en</language>
    <item>
      <title>Pretty print JSON in Python: json.dumps, json.tool, and the three errors you will hit</title>
      <dc:creator>Dan Lumatic</dc:creator>
      <pubDate>Sat, 17 Oct 2026 00:02:01 +0000</pubDate>
      <link>https://dev.to/dan_lumatic/pretty-print-json-in-python-jsondumps-jsontool-and-the-three-errors-you-will-hit-4p4m</link>
      <guid>https://dev.to/dan_lumatic/pretty-print-json-in-python-jsondumps-jsontool-and-the-three-errors-you-will-hit-4p4m</guid>
      <description>&lt;p&gt;You printed an API response and got one line, 400 characters wide, with the field you need somewhere in the middle. The fix is one call, &lt;code&gt;print(json.dumps(data, indent=2))&lt;/code&gt;. What follows is what happens on real data.&lt;/p&gt;

&lt;p&gt;If what you printed was the dict itself, the problem is different. &lt;code&gt;print(data)&lt;/code&gt; shows Python's repr: single quotes, &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt;, like &lt;code&gt;{'a': 1, 'b': [1, 2]}&lt;/code&gt;. JSON parsers reject that text. &lt;code&gt;json.dumps&lt;/code&gt; is what produces JSON.&lt;/p&gt;

&lt;p&gt;Three errors show up on the first real dataset: a &lt;code&gt;datetime&lt;/code&gt; that raises &lt;code&gt;TypeError: Object of type datetime is not JSON serializable&lt;/code&gt;, &lt;code&gt;café&lt;/code&gt; printed as &lt;code&gt;caf\u00e9&lt;/code&gt;, and a file that dies on &lt;code&gt;JSONDecodeError&lt;/code&gt;. Each has a quick fix. Two of the popular quick fixes fail later: &lt;code&gt;default=str&lt;/code&gt; writes dates other programs have to guess at, and &lt;code&gt;ensure_ascii=False&lt;/code&gt; writing to a file needs an explicit encoding or the text can arrive mangled. Below: the calls, the flags worth knowing, and where each fix is safe.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;print(json.dumps(data, indent=2))&lt;/code&gt; pretty prints any dict or list. Add &lt;code&gt;sort_keys=True&lt;/code&gt; and two dumps become diffable.&lt;/li&gt;
&lt;li&gt;No code at all: &lt;code&gt;python -m json.tool data.json&lt;/code&gt; formats from the terminal, reads stdin, and fails loudly on invalid JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;datetime&lt;/code&gt; raises &lt;code&gt;not JSON serializable&lt;/code&gt;: &lt;code&gt;default=str&lt;/code&gt; for a quick look, &lt;code&gt;isoformat()&lt;/code&gt; for output other programs parse.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;café&lt;/code&gt; becomes &lt;code&gt;caf\u00e9&lt;/code&gt;: &lt;code&gt;ensure_ascii=False&lt;/code&gt;, plus &lt;code&gt;encoding="utf-8"&lt;/code&gt; on the &lt;code&gt;open()&lt;/code&gt; when you write a file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JSONDecodeError&lt;/code&gt; on load: the message names the line and column. Trailing commas and single quotes are the usual causes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The one-liner: &lt;code&gt;indent&lt;/code&gt; expands every list and object
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;indent&lt;/code&gt; is the flag that does the formatting. Without it, &lt;code&gt;json.dumps&lt;/code&gt; returns one compact line. With it, every object and list spreads over indented lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ada&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;59.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {"name": "Ada", "active": true, "score": 59.0, "tags": ["a", "b", "c"], "manager": null}
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manager"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;59.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"c"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;indent&lt;/code&gt; expands lists as well as objects: every list item gets its own line.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three details in that output are worth knowing, and all of them are valid JSON:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;True&lt;/code&gt; prints as &lt;code&gt;true&lt;/code&gt;, and &lt;code&gt;None&lt;/code&gt; prints as &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Floats keep Python's form, so &lt;code&gt;59.0&lt;/code&gt; stays &lt;code&gt;59.0&lt;/code&gt;, not &lt;code&gt;59&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sort_keys=True&lt;/code&gt; orders keys alphabetically. Use it whenever you plan to diff two dumps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One note if you were about to reach for &lt;code&gt;pprint&lt;/code&gt; instead: it prints Python syntax (&lt;code&gt;'city': 'café'&lt;/code&gt;, &lt;code&gt;True&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt;), and a JSON parser rejects that too. &lt;code&gt;json.dumps&lt;/code&gt; is the call when the output needs to be JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pretty print a JSON file, a string, or an API response
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;json.dump&lt;/code&gt; (no "s") writes to a file object, &lt;code&gt;json.dumps&lt;/code&gt; returns a string, and &lt;code&gt;python -m json.tool&lt;/code&gt; needs no code at all.&lt;/strong&gt; The module docs list both signatures: &lt;a href="https://docs.python.org/3/library/json.html" rel="noopener noreferrer"&gt;json, JSON encoder and decoder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Read a file and print it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write it back formatted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the terminal, no script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool data.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads stdin too:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; pipe any HTTP response through it.&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.example.com/data | python &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;And if the JSON is already a string in a variable or your REPL, parse it, then dump it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pretty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One catch: &lt;code&gt;json.tool&lt;/code&gt; indents with 4 spaces, &lt;code&gt;indent=2&lt;/code&gt; with 2. Both are valid, but a file formatted one way next to a file formatted the other differs on every indented line. Pick one value and use it everywhere: &lt;code&gt;indent=2&lt;/code&gt; is the common choice, 4 makes sense when the JSON sits next to Python source that indents with 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pin &lt;code&gt;indent&lt;/code&gt; and &lt;code&gt;separators&lt;/code&gt; when files get compared
&lt;/h2&gt;

&lt;p&gt;A snapshot test starts failing, or a git diff marks every line of a JSON file as changed, and the data underneath is identical. Check what wrote the file. &lt;code&gt;json.dumps&lt;/code&gt; defaults to the separators &lt;code&gt;(', ', ': ')&lt;/code&gt;, a comma plus a space. Set &lt;code&gt;indent&lt;/code&gt; and the item separator silently becomes &lt;code&gt;(',', ': ')&lt;/code&gt;, because the newline now carries the spacing. Same dict, both ways:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# '{"a": 1, "b": [1, 2]}'
&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# '{\n  "a": 1,\n  "b": [\n    1,\n    2\n  ]\n}'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compact form has a space after each comma, the pretty form has none. So two files with identical data can differ in their bytes, and a test that compares them byte for byte fails even though &lt;code&gt;json.loads&lt;/code&gt; returns equal data from both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If a test, a checksum or a diff compares your formatted JSON, pass &lt;code&gt;indent&lt;/code&gt; and &lt;code&gt;separators&lt;/code&gt; explicitly in every writer that touches the file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The three errors you will actually hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One of the three is a &lt;code&gt;TypeError&lt;/code&gt; from your own data. One is valid JSON that only looks wrong. The third means the input is not valid JSON.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;TypeError: Object of type datetime is not JSON serializable&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;JSON has no date type, so &lt;code&gt;json.dumps&lt;/code&gt; refuses to guess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# TypeError: Object of type datetime is not JSON serializable
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quick fix for a debugging session is &lt;code&gt;default=str&lt;/code&gt;, which calls &lt;code&gt;str()&lt;/code&gt; on anything JSON can't handle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {"created": "2026-10-11 20:00:00"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same traceback fires for &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;Decimal&lt;/code&gt; and &lt;code&gt;UUID&lt;/code&gt; objects, and &lt;code&gt;default=str&lt;/code&gt; handles all of them: a set comes out as &lt;code&gt;"{1, 2}"&lt;/code&gt;, a &lt;code&gt;Decimal&lt;/code&gt; as &lt;code&gt;"1.1"&lt;/code&gt;, a &lt;code&gt;UUID&lt;/code&gt; as its canonical string. That stops the traceback for any type JSON does not know. It does not make the values good: the set came out as the string &lt;code&gt;"{1, 2}"&lt;/code&gt;, which no parser reads back as a set.&lt;/p&gt;

&lt;p&gt;But look at what it wrote for the datetime: a space in the middle and no timezone, so another program has to guess the format. For output anyone else will parse, convert the value yourself with &lt;code&gt;isoformat()&lt;/code&gt;, which produces the ISO 8601 form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {"created": "2026-10-11T20:00:00"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Non-ASCII text shows up as &lt;code&gt;\u00e9&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;json.dumps&lt;/code&gt; escapes non-ASCII characters by default. &lt;code&gt;café&lt;/code&gt; becomes &lt;code&gt;caf\u00e9&lt;/code&gt;, and both forms are valid JSON that decode to the same string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;café&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="c1"&gt;# {"city": "caf\u00e9"}
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;café&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# {"city": "café"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;ensure_ascii=False&lt;/code&gt; when a human will read the output. If that output goes to a file, also open the file with an explicit encoding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ensure_ascii&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default encoding for &lt;code&gt;open()&lt;/code&gt; is UTF-8 only where Python's UTF-8 Mode is enabled; otherwise it follows the locale, and non-ASCII written without an explicit encoding can fail or arrive mangled. Passing &lt;code&gt;encoding="utf-8"&lt;/code&gt; makes the result the same everywhere. Details in the &lt;a href="https://docs.python.org/3/library/io.html#text-encoding" rel="noopener noreferrer"&gt;io module docs, Text encoding&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;JSONDecodeError&lt;/code&gt; when you load
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;json.loads&lt;/code&gt; is stricter than most people expect. This looks harmless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 1,}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# json.decoder.JSONDecodeError: Illegal trailing comma before end of object: line 1 column 8 (char 7)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message names the position, so start there. The usual suspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A trailing comma&lt;/strong&gt;, as above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single quotes.&lt;/strong&gt; JSON strings use double quotes only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pasted text that was never JSON&lt;/strong&gt;, such as a log prefix in front of the opening brace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;json.tool&lt;/code&gt; reports the same problem at the command line, and exits with status 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"a": 1,}'&lt;/span&gt; | python &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;span class="c"&gt;# Illegal trailing comma before end of object: line 1 column 8 (char 7)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt; when a script chokes on a file, run &lt;code&gt;python -m json.tool file.json&lt;/code&gt; before you touch your code. If the tool fails, the file is the problem. If it prints cleanly, the bug is in your script.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Which call for which job
&lt;/h2&gt;

&lt;p&gt;Everything above, sorted by where the data sits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dict or list in front of you: &lt;code&gt;print(json.dumps(data, indent=2))&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;JSON already a string in a variable: &lt;code&gt;json.dumps(json.loads(blob), indent=2)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Writing a file: &lt;code&gt;json.dump(data, f, indent=2)&lt;/code&gt;, with &lt;code&gt;ensure_ascii=False&lt;/code&gt; and &lt;code&gt;encoding="utf-8"&lt;/code&gt; when non-ASCII is involved&lt;/li&gt;
&lt;li&gt;No code: &lt;code&gt;python -m json.tool data.json&lt;/code&gt;, or pipe curl output through it&lt;/li&gt;
&lt;li&gt;Files that get diffed or checksummed: pin &lt;code&gt;separators&lt;/code&gt; too&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>beginners</category>
      <category>python</category>
      <category>json</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
