Invalid control character at: line N column N (char N)

ErrorPython

json.decoder.JSONDecodeError: Invalid control character at: line 3 column 20 (char 32)

Where JSON.parse stops

  "id": 7,
  "note": "line one
line two"

What it means

A JSON string has a raw line break or tab inside it. JSON does not allow that, so Python stops at the character. The position is that character, so look at the end of the line it names.

Last verified on Python 3.9, 3.11, 3.12, 3.13 and 3.14.

The validator opens with the error marked. Examples travel in the URL fragment, which browsers never send to a server.

Express Lint lays broken JSON out by bracket depth and marks the line where it stops. It changes only spaces and line breaks outside strings. Strings in either quote stay as written. If the quotes do not pair up, it leaves your text as it is. Ctrl+Z undoes it. See how it works.

Usually
A line break inside a stringA tab inside a stringA missing closing quote in a document with line breaksText from a file or a database
Fix
Write the line break as \n and the tab as \t, as the fixed example does.

The fix

Write the line break as \n and the tab as \t, as the fixed example does. If you build the JSON, use json.dumps, because it escapes both.

broken.jsonFails to parse
{
  "id": 7,
  "note": "line one
line two"
}
JSON.parse stops at the line break at the end of line 3.

Line 3: add \n.

fixed.jsonParses
{
  "id": 7,
  "note": "line one\nline two"
}
JSON.parse reads it.

How the message appears

CPython's json module reports line, column and character offset. The offset is the control character itself. The first line is the example, with a line break in the string. The second is a tab. The third is a missing closing quote in JSON with line breaks. Python 3.9 to 3.14 print the same for all three.

Console
  • json.decoder.JSONDecodeError: Invalid control character at: line 3 column 20 (char 32)
  • json.decoder.JSONDecodeError: Invalid control character at: line 1 column 12 (char 11)
  • json.decoder.JSONDecodeError: Invalid control character at: line 2 column 25 (char 26)

What causes it

  1. A line break inside a string

    Someone pressed Enter inside a quoted value, or text with several lines was written into the JSON as it is. In the example the string "line one ends at the line break, and Python stops there.

  2. A tab inside a string

    A tab looks like spaces, so you do not see it. Text copied from a spreadsheet or a terminal can carry them. A tab gives the same message as a line break, and the position is the tab.

  3. A missing closing quote in a document with line breaks

    The string that lost its closing quote runs on to the end of its line, and the line break is a control character. So you get this message and not one about a quote. The position is the end of that line. See Unterminated string.

  4. Text from a file or a database

    Notes, addresses and comments often have line breaks in them. If you build the JSON with an f-string, those breaks go straight into the document. json.dumps writes each one as \n.

Worth knowing

Watch outjson.loads(text, strict=False) reads a string with a raw line break or tab in it. Other parsers still reject that document, JavaScript among them. Use it for a file you cannot change, not for JSON you send on.

NoteJavaScript rejects the same string. See Bad control character in string literal.

Your JSON and text are processed entirely in your browser and are never sent anywhere. To understand how the site is used we record each page view (the page, the referrer and an approximate location from your IP address) and keep it for 90 days. Nothing is stored on your device unless you choose it. Accept to also store a device identifier here and record your browser and device characteristics, which is what lets a return visit be told from a new one. Decline and nothing is sent at all. We respect Do Not Track. See our privacy policy.