LESSON 23 · NEXT STEPS27 min

Read and Write JSON and CSV

Serialize and parse JSON, read CSV rows by header, and convert text fields deliberately.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Read and Write JSON and CSV

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    JSON and CSV are two common formats for moving data between programs, but they behave differently when Python reads them. A Python dictionary printed to the screen can look almost like a JSON object. Almost is the dangerous word. In this video I will pass a Python-looking dictionary string to the JSON parser and show the actual error. Then I will generate valid JSON with the standard library instead of trying to assemble it by hand. After that, we will read a tiny CSV table and inspect a score that looks numeric but arrives as text. Before each run, predict both the output and the type of the important value.

  2. 00:44

    The matching PythonLessonLab lesson has a browser console, worked examples, a checked task, and a quiz. It uses in-memory text streams so you can run the examples without creating files on your own computer. JSON is useful for nested objects and arrays, while CSV is useful for rows with a consistent set of columns. Neither format automatically carries all of Python's type information, so a reader must be deliberate about parsing values. This line raises JSONDecodeError. The data resembles a Python dictionary representation: the key and string value use single quotes. JSON requires double-quoted strings, including object keys, so the parser rejects it.

  3. 01:30

    A JSON error may also point to a trailing comma, a missing colon, or another syntax problem; the error message and position help locate the specific fault. The repair is not to perform a blind text replacement of single quotes with double quotes. A real name might contain an apostrophe, and values can contain quotation marks, backslashes, and line breaks. A series of manual replacements can damage perfectly good data. If the data starts as Python values, call json.dumps to serialize them. If the data arrives from an external source, confirm that the source really promised JSON and inspect the original text when parsing fails.

  4. 02:14

    The two directions have different names: dumps produces JSON text; loads parses JSON text back into Python objects. The text in the failing example resembles a Python dictionary representation. A human can see name and Ada, but a JSON parser follows JSON grammar. The first single quote after the opening brace is the critical character. JSON can represent nested objects and arrays as well as strings, numbers, booleans, and null. A Python dictionary can be serialized to a JSON object, and json.loads normally restores that object as a dictionary.

  5. 02:55

    Notice that JSON spells a boolean as lowercase true or false, while Python uses capitalized True or False. That is another reason a Python repr is not a safe substitute for JSON. CSV has a different purpose: it is a table of rows and fields. A header row can name the columns, and csv.DictReader turns each later row into a dictionary keyed by those header names. The reader does not infer that the characters seven mean an integer. It gives us the string seven or, in our sample, the string containing digit seven. Before adding one to a score, we need to convert it with int and decide what to do if the text is invalid.

  6. 03:38

    The format determines which parsing step is needed. Instead of hand-building a JSON string with concatenation, give json dot dumps a Python object. It handles quoting and escaping. The paired loads call then turns valid JSON text back into Python data you can index. Now json.dumps makes valid JSON from the Python dictionary, and json.loads parses that text back. We access the name from the restored dictionary and print Ada. For the CSV half, StringIO provides a text stream containing a header row and one data row. DictReader reads the header, so row has keys called name and score.

  7. 04:21

    Printing the score displays seven, but that visible output does not tell us its type. The number seven and the string seven look identical when printed alone. That is why we should not infer a value's type from one line of output. This example does not write a permanent file. In a local program, the same reader can receive a file opened with UTF-eight encoding and newline set to an empty string. For writing, csv.DictWriter can output headers and rows, while json.dump can write JSON to a file object. The lesson's task concentrates on reading and conversion first. For CSV, DictReader uses the header row as keys.

  8. 05:03

    That makes a column name easier to follow than a numeric column position. In a real file, use an explicit text encoding and newline handling when opening the file. The verification run makes the hidden type visible: row score is a string. After int converts it, adding one produces eight. If the field contained a word such as seven instead of digits, int would raise ValueError. A larger data importer should identify that row and report a useful message rather than silently changing a bad value to zero. On the lesson page, you will write total_scores. It parses a JSON record containing a bonus, reads CSV score rows, converts each score to an integer, and returns the total.

  9. 05:49

    For the prepared record and two rows, two plus seven plus three equals twelve. Test a CSV string with only a header too; there are no score rows, so the function should return only the bonus. Use Check task after running your own code, then answer the quiz about serialization, valid JSON quoting, and CSV value types. As an extension, write a tiny CSV with DictWriter and read it back. Keep the distinction clear: a format may look familiar, but parsing rules and value types are decided by that format's reader. The score printed as seven may look numeric, but DictReader gave us a string.

  10. 06:29

    Inspect its type before adding one, then call int explicitly. Test a missing score or nonnumeric score to decide how your article's program should respond.

Understand the concept

JSON and CSV are common ways to exchange data. JSON can represent nested objects, arrays, numbers, strings, booleans, and null. A JSON object looks similar to a Python dictionary, but the formats are not identical. In particular, JSON strings use double quotes and the spellings true, false, and null differ from Python's True, False, and None.

Use json.dumps to turn a Python value into a JSON string, then json.loads to parse JSON text back into Python values. Use json.dump and json.load when working with file objects. Avoid building JSON by concatenating strings: quotes, backslashes, and special characters need proper escaping. A JSONDecodeError means the input text is not valid JSON, so inspect the source text and the reported position rather than guessing.

CSV stores rows of text fields. The first row can be a header, allowing csv.DictReader to yield a dictionary for each later row. Even if a cell looks like a number, DictReader gives you a string. Convert the field with int or float only after you decide what values are valid. Use csv.DictWriter to write header and row dictionaries when you need output.

The browser examples use StringIO, an in-memory text stream, so they run safely without depending on a saved file. The same csv reader and writer APIs accept a real UTF-8 file opened with newline='' in a local Python program. The checked task practices the core distinction: parse a JSON record, read CSV scores, convert the score strings, and compute a number.

  • json.dumps serializes and json.loads parses
  • JSON text is not a Python repr
  • csv.DictReader maps headers to string values
  • Convert numeric CSV cells explicitly

See it step by step

Read the code, predict the output, then compare it with the result.

01. Serialize and parse a JSON object

PYTHON
import json
payload = json.dumps({"name": "Ada", "score": 7})
restored = json.loads(payload)
print(payload)
print(restored["score"])
EXPECTED OUTPUT
{"name": "Ada", "score": 7}
7

dumps creates valid JSON text. loads parses it back into a Python dictionary whose score is a number.

02. Read CSV rows by header

PYTHON
import csv
from io import StringIO
text = "name,score\nAda,7\nBo,3\n"
rows = list(csv.DictReader(StringIO(text)))
print(rows[0]["name"])
print(rows[1]["score"])
EXPECTED OUTPUT
Ada
3

DictReader uses name and score from the header as keys. The score field is still text.

03. Convert a CSV field before arithmetic

PYTHON
import csv
from io import StringIO
row = next(csv.DictReader(StringIO("score\n7\n")))
print(type(row["score"]).__name__)
print(int(row["score"]) + 1)
EXPECTED OUTPUT
str
8

The field is a string even though it contains a digit. int converts it to a number so addition is arithmetic.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Spot the difference between JSON and Python text

A printed Python dictionary can resemble JSON, yet it may use single quotes, Python booleans, or None. Passing that representation to json.loads is unreliable because JSON has its own grammar. The first parse attempt below uses a single-quoted key and value, so the parser raises JSONDecodeError. The handler prints only the exception class to keep this exercise's expected output stable; when debugging real data, inspect the full error for its position and message.

The second attempt uses double-quoted JSON strings and succeeds. For data created inside Python, do not manually switch quote characters with string replacement. A person's name can contain an apostrophe, and text can contain backslashes, quotes, or line breaks. Let json.dumps escape them correctly. Then json.loads can round-trip the result. Think of dumps as producing the wire format and loads as reading that format back into Python values.

PYTHON
import json
try:
    json.loads("{'name': 'Ada'}")
except json.JSONDecodeError as error:
    print(type(error).__name__)
print(json.loads('{"name": "Ada"}')["name"])
EXPECTED OUTPUT
JSONDecodeError
Ada
Follow the reasoning
  1. The first string is a Python-like repr, not valid JSON text.
  2. The second string has JSON's required double-quoted key and value.
  3. Replace Ada with text containing an apostrophe and use json.dumps to escape it.
02 / 03

Serialize, parse, and inspect the restored types

The json module maps basic Python containers and scalar values to JSON equivalents. In this example a dictionary holds a string, an integer, and a boolean. json.dumps returns a string containing valid JSON; json.loads turns that string into a fresh Python dictionary. The output contains lowercase true because JSON uses that spelling, while the restored Python value prints True. Seeing that difference helps prevent the mistake of treating JSON as ordinary Python source code.

A round trip is useful for checking assumptions, but it does not preserve every possible Python object automatically. Objects like a custom class or set need an explicit conversion strategy. For everyday API records made of dictionaries, lists, strings, numbers, booleans, and None, the standard mapping is usually enough. Before saving or transmitting a payload, inspect both the serialized text and the restored values. Check that numbers stayed numbers and that a nested key is where your consumer expects it.

PYTHON
import json
record = {"name": "Ada", "score": 7, "active": True}
payload = json.dumps(record)
restored = json.loads(payload)
print(payload)
print(restored["score"] + 1)
print(restored["active"])
EXPECTED OUTPUT
{"name": "Ada", "score": 7, "active": true}
8
True
Follow the reasoning
  1. dumps creates JSON text and uses JSON's lowercase true.
  2. loads restores an integer score and a Python True value.
  3. Try adding a list of tags to the record and inspect the new payload.
03 / 03

Read a CSV table and convert its cells

CSV represents a table as rows of text fields. DictWriter can write a header and a row dictionary; DictReader reads that header and returns one dictionary per later row. A key such as row['score'] names the column instead of relying on a numeric position. The reader still returns strings: the characters 7 and 3 are not integers merely because they look numeric. Calling sum directly on those values raises a TypeError. Convert each known numeric field deliberately before arithmetic.

The example uses StringIO as a text stream for both writing and reading, without creating a file. In a local project, open a real CSV file with encoding='utf-8' and newline='' and give that file object to the writer or reader. The newline setting lets the csv module handle line breaks. If a score is missing or malformed, int will raise an error. A larger application would validate that row and report which record needs correction rather than silently treating bad data as zero.

PYTHON
import csv
from io import StringIO
stream = StringIO()
writer = csv.DictWriter(stream, fieldnames=["name", "score"])
writer.writeheader()
writer.writerow({"name": "Ada", "score": 7})
writer.writerow({"name": "Bo", "score": 3})
print(stream.getvalue().splitlines())
rows = list(csv.DictReader(StringIO(stream.getvalue())))
print(type(rows[0]["score"]).__name__)
scores = [int(row["score"]) for row in rows]
print(scores)
print(sum(scores))
EXPECTED OUTPUT
['name,score', 'Ada,7', 'Bo,3']
str
[7, 3]
10
Follow the reasoning
  1. DictWriter writes the header and two row dictionaries to an in-memory text stream.
  2. DictReader uses that header to give each row its name and score keys.
  3. int converts known numeric text before sum computes the total.
  4. Change one score to the word seven and observe where validation is needed.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete total_scores(json_text, csv_text). Parse the JSON record and read the CSV rows with csv.DictReader. Add the record's bonus to the integer value of each CSV score and return the total. For the prepared data, print 12. Empty CSV data after the header should return only the bonus.
Need a hint?

Use json.loads(json_text)['bonus'] to start the total. Iterate over csv.DictReader(StringIO(csv_text)) and add int(row['score']) for each row. Return the number.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Which function turns a Python dictionary into valid JSON text?

    02.Why does json.loads("{'name': 'Ada'}") fail?

    03.What type is row['score'] when row came from csv.DictReader?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Passing a Python repr as JSON

    COMMON MISTAKE
    json.loads("{'name': 'Ada'}")
    FIX
    json.loads('{"name": "Ada"}')

    What happens: JSONDecodeError because JSON keys and strings need double quotes.
    Use json.dumps to produce JSON from Python values instead of assembling a repr-like string yourself.

    Adding a CSV number as text

    COMMON MISTAKE
    row = next(csv.DictReader(StringIO('score\n7\n')))
    print(row['score'] + 1)
    FIX
    row = next(csv.DictReader(StringIO('score\n7\n')))
    print(int(row['score']) + 1)

    What happens: TypeError: can only concatenate str (not int) to str.
    Every CSV cell starts as text. Convert with int only when the data should contain a whole number.

    Browse this lesson’s problem fixes

    Sources and further reading