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 wrote | JavaScript'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:
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 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.




