Skip to content

Parse JSON in Python: json.loads, files and API responses

We tested Python's json on files, API answers and JavaScript. Python writes NaN that JSON.parse rejects, and orjson sped up writing far more than reading.

HProxy TeamOctober 12, 2026Updated October 12, 20265 min read

Parse JSON in Python: json.loads, files and API responses

Python reads JSON with its built-in json module: json.loads for text, json.load for files, and r.json() for answers fetched with requests. Each works until the input is not quite what you expected.

We tested all three on our server on 12 October 2026. We also fed Python's output to JavaScript, which reads JSON more strictly than Python writes it.

Parse a string

import json

data = json.loads('{"name": "Ada", "age": 36, "active": true, "manager": null, "tags": ["a", "b"]}')
print(data["name"], data["tags"][0], data["manager"])
Ada a None

Objects became dicts, arrays lists, true became True and null became None. From here it is plain Python.

Write and read a file, as UTF-8

people = [{"name": "Zoë"}, {"name": "Łukasz"}]
with open("people.json", "w", encoding="utf-8") as f:
    json.dump(people, f, ensure_ascii=False, indent=2)
with open("people.json", encoding="utf-8") as f:
    people = json.load(f)
print(people[1]["name"])
Łukasz

Always pass encoding="utf-8". Without it, "The default encoding is platform dependent", as Python's documentation puts it, and on Windows that is often cp1252. We read the same file as cp1252, and it raised UnicodeDecodeError on the bytes of "Łukasz".

ensure_ascii=False keeps "Zoë" readable in the file. By default, json writes it as "Zoë".

Expecting value: line 1 column 1 (char 0)

This error means the text does not start with JSON at all. We got it from an empty answer and from an HTML error page. Other mistakes point to the exact spot. Single quotes and a trailing comma gave "Expecting property name enclosed in double quotes", and a comment gave "Expecting ',' delimiter".

With requests, an API that answers with an error page raises the same error inside r.json():

import requests

r = requests.get("http://127.0.0.1:31294/error.html", timeout=10)
try:
    data = r.json()
except requests.exceptions.JSONDecodeError as e:
    print("not JSON:", r.headers["Content-Type"], "|", e)
not JSON: text/html | Expecting value: line 1 column 1 (char 0)

The address is a test server on our own machine, standing in for an API in trouble. The Content-Type says text/html, so check it before you parse, or catch the error as here. Under heavy use, an API or a proxy in between may answer with exactly such a page.

Python writes JSON that other languages reject

Python's json module is relaxed by default, and the JSON it writes can fail elsewhere. We gave three values to JavaScript's JSON.parse:

Python wroteJavaScript's JSON.parse gave
{"x": NaN}SyntaxError
{"x": Infinity}SyntaxError
{"id": 12345678901234567890}{"id": 12345678901234567000}

The JSON standard says "Numeric values that cannot be represented in the grammar below (such as Infinity and NaN) are not permitted". Python writes them anyway, unless you ask it to refuse:

try:
    json.dumps({"x": float("nan")}, allow_nan=False)
except ValueError as e:
    print(e)
Out of range float values are not JSON compliant: nan

The big integer is a different trap. Python keeps every digit, but JavaScript stores numbers as doubles, so the last digits changed. Send long IDs as strings when another language will read them.

Dates

json cannot write a datetime. It raised TypeError: Object of type datetime is not JSON serializable. Write the date as ISO text yourself:

from datetime import datetime, timezone

now = datetime(2026, 10, 12, 9, 30, tzinfo=timezone.utc)
print(json.dumps({"at": now.isoformat()}))
{"at": "2026-10-12T09:30:00+00:00"}

default=str also works, but it writes a space where ISO 8601 puts a T.

Duplicate keys

json.loads('{"a": 1, "a": 2}') gave {'a': 2} without a word. The standard only says names "SHOULD be unique", and Python keeps the last one. To refuse such input instead:

def no_duplicates(pairs):
    keys = [k for k, _ in pairs]
    if len(keys) != len(set(keys)):
        raise ValueError(f"duplicate keys: {keys}")
    return dict(pairs)

try:
    json.loads('{"a": 1, "a": 2}', object_pairs_hook=no_duplicates)
except ValueError as e:
    print(e)
duplicate keys: ['a', 'a']

JSON inside a web page

Many pages carry structured data as JSON in a script tag. One of our own pages has five such blocks:

from bs4 import BeautifulSoup

r = requests.get("https://hproxy.com/blog/python-requests-post", timeout=30)
soup = BeautifulSoup(r.content, "lxml")
for block in soup.select('script[type="application/ld+json"]'):
    item = json.loads(block.string)
    print(item["@type"])
Organization
WebSite
BlogPosting
BreadcrumbList
FAQPage

That is often the cleanest way to scrape a page: the data is already structured, with no CSS selectors to keep up. Our Python scraping tutorial covers the HTML side.

json or orjson

orjson calls itself "the fastest Python library for JSON". We timed both on a generated document of 10 MB, best of five runs:

Milliseconds for a 10 MB JSON document (best of 5)
json.loads
161.5 ms
orjson.loads
147.1 ms
json.dumps
104.3 ms
orjson.dumps
17.5 ms
Source: HProxy test on our server, 12 October 2026: Python 3.12.3, orjson 3.13.0; a busy server, so the times are rough · hproxy.comProxy.

For writing, orjson was six times faster. For reading this document, it was about 9 percent faster. If your program mostly reads JSON, the built-in module may be all you need.

Here is the whole test, from our server:

What broke and what other parsers made of Python's JSON, from our test
Our own capture, 12 October 2026: json_tests.py on our server (long lines cut)

What this page could not check

  • One Python, one Node and one generated document. The server was busy, so the milliseconds are rough.
  • The Windows encoding problem was shown by reading with cp1252, not on a Windows machine.
  • We did not test streaming parsers such as ijson, or JSON Lines.
  • We will rerun these tests by 12 January 2027.

Sources

  • Python 3.12 documentation, json: docs.python.org.
  • RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format (December 2017): rfc-editor.org.
  • Python 3.12 documentation, open(): docs.python.org.
  • Requests documentation, Quickstart (JSON response content): requests.readthedocs.io.
  • orjson on PyPI: pypi.org.
  • Our own tests on our server, 12 October 2026: json_tests.py at 01:46 UTC and the code on this page at 01:50 UTC; one request to hproxy.com for the JSON-LD example.

Frequently asked questions

How do I read a JSON file in Python?

Open it with an explicit encoding and pass it to json.load: with open("data.json", encoding="utf-8") as f: data = json.load(f). Without encoding=, Python uses the platform's default, and on Windows that can fail: our UTF-8 file read as cp1252 raised UnicodeDecodeError.

What does 'Expecting value: line 1 column 1 (char 0)' mean?

The text does not start with JSON. In our tests it came from an empty answer and from an HTML error page. Check what the server sent, for example r.headers["Content-Type"] and the first characters of r.text, before you parse.

How do I parse a JSON response from an API in Python?

Call r.json() on the requests response. It raises requests.exceptions.JSONDecodeError when the answer is not JSON, as our HTML error page showed, so catch that error or check the Content-Type first.

How do I parse a JSON array in Python?

json.loads turns a JSON array into a Python list, so loop over it: for item in json.loads(text). Each object in the array becomes a dict.

How do I parse a JSON string in Python?

Use json.loads(text). JSON objects become dicts, arrays lists, strings str, numbers int or float, true and false True and False, and null None. Single quotes are not JSON: they raised 'Expecting property name enclosed in double quotes'.

Get proxies that are alive right now

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup

HProxy.

Honest guides and comparisons on proxies, scraping and staying unblocked, from the team that runs the network.

RSS feed