Python json.dumps and json.loads explained
Python's built-in json module handles both directions between JSON text and Python data. The names are easy to mix up, so remember that load reads JSON and dump writes JSON.
Read JSON with json.loads
Use loads when you have JSON text in a string, such as an API response. It returns normal Python data: objects become dictionaries and arrays become lists. Use json.load without the final s when you are reading from an open file object.
Write JSON with json.dumps
Use dumps to serialize a dictionary or list into a JSON string. The option indent=2 makes the result easier for people to read and review.
The double-encoding trap: JSON stored inside a JSON value
My gym app has a generic key-value endpoint used to save client-side state — things like unlocked achievements, tutorial progress, that kind of loosely structured data that does not deserve its own database column. The save endpoint is intentionally simple:
$key = $body['key'] ?? '';
$value = $body['value'] ?? '';
// stored as-is in a TEXT column, no further processing
The client is the one that calls JSON.stringify(state) before sending it, so value arrives as a JSON string. The read endpoint just echoes it back inside the outer response:
{"key": "player-state", "value": "{\"tutorialStep\":4,\"seenTips\":[\"pvp\",\"shop\"]}"}
Notice value is a string whose content happens to look like JSON — it is JSON inside JSON. A naive json.loads() call on the whole response gives you that inner blob as a plain Python string, not a dict. It needs a second, deliberate decode:
import json
outer = json.loads(response_text)
state = json.loads(outer["value"]) # decode the nested string separately
print(state["tutorialStep"])
Forgetting that second json.loads call is an easy mistake, and the failure is confusing: outer["value"] looks fine when you print it, so the bug hides until code tries to use it as a dict and gets a string-indexing error instead.
Handle unsupported values deliberately
Strings, numbers, booleans, lists, dictionaries and None serialize naturally. Dates, decimals, sets and custom classes need a deliberate conversion step. Avoid str() as a shortcut: it produces Python-looking text, not guaranteed JSON.
{ or [, check whether it is meant to be decoded again before you treat it as plain text.For quick visual conversions, try Python dict to JSON and see the complete conversion guide.