Typorithm · medium

JSON boundaries and correct CSV quoting

pythonscriptingdata-boundariesindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

JSON boundaries and correct CSV quoting

0%

Concept and contract

Validate structured records and use the format writer instead of concatenating delimiters by hand.

Valid text is not automatically valid structured data. The worked converter parses a JSON document, requires a list of records with nonempty string id and title fields, and exports those two columns as CSV. Extra source fields are deliberately ignored rather than silently becoming output columns.

The format library must preserve a title containing a comma, a quote or a newline. Such content is ordinary data, not a reason to split or concatenate a line manually. The input fixtures are small local strings; this lesson does not claim bounded-memory parsing of arbitrary large network documents.

Mechanism and worked example

json.loads converts a JSON document into ordinary Python values, so shape and field validation still follow parsing.

json.loads converts a JSON document into ordinary Python values, so shape and field validation still follow parsing. csv.writer applies CSV quoting rules, and StringIO supplies an in-memory text buffer. The output header and newline convention are explicit parts of the export contract.

The returned string is captured before the context-managed buffer closes. The converter strips outer field whitespace but keeps internal punctuation and newlines. This separates domain normalization from format encoding and prevents an export from corrupting a legitimate multiword title.

Complete worked example

python

import csv
import io
import json

def convert(text):
    rows = json.loads(text)
    if not isinstance(rows, list):
        raise ValueError("Expected a list")
    fields = ["id", "title"]
    with io.StringIO(newline="") as buffer:
        writer = csv.writer(buffer, lineterminator="\n")
        writer.writerow(fields)
        for row in rows:
            if not isinstance(row, dict) or not all(
                isinstance(row.get(key), str) and row[key].strip() for key in fields
            ):
                raise ValueError("Expected nonempty record fields")
            writer.writerow([row[key].strip() for key in fields])
        return buffer.getvalue()
Reasoning
Parsing and field validation precede a real CSV writer with an explicit header.

Counterexamples and common mistakes

The counterexample joins values with commas and fails to quote a title containing a comma.

The counterexample joins values with commas and fails to quote a title containing a comma. A consumer can then see an extra column even though the original title was a single field. A naive newline split similarly cannot parse a quoted multiline CSV cell reliably.

CSV grammar escaping is also not a spreadsheet-formula policy. A real export opened by spreadsheet software may need a separately designed treatment of formula-like cells. The core task here establishes correct structured shape and format quoting without claiming every downstream interpretation is made safe automatically.

Counterexample: a plausible but incorrect implementation

python

import json

def convert(text):
    rows = json.loads(text)
    lines = ["id,title"]
    for row in rows:
        lines.append(row["id"] + "," + row["title"])
    return "\n".join(lines) + "\n"
Reasoning
A manual comma join corrupts the single-field title into extra CSV columns.

Independent practice: small Python task

Add a required country column to the CSV export while preserving the validation and quoting behavior.

Add a required country column to the CSV export while preserving the validation and quoting behavior. Keep header order id, title, country. This small extension reuses the same writer and changes the explicit projected field list, not the source document format.

Predict the exact output for a title containing a comma and a supplied UA country. Reject a missing or non-string country under the revised practice contract. The two hints direct you to the shared field list so validation and writer order cannot drift independently.

Step 1: Use the field list as the single projection contract. Step 2: Include country after id and title while retaining writer-based quoting.

Practice starter: solve before reading the solution

python

import csv
import io
import json

def convert(text):
    rows = json.loads(text)
    if not isinstance(rows, list):
        raise ValueError("Expected a list")
    fields = []  # Add the three required output fields in order.
    with io.StringIO(newline="") as buffer:
        writer = csv.writer(buffer, lineterminator="\n")
        writer.writerow(fields)
        for row in rows:
            if not isinstance(row, dict) or not all(
                isinstance(row.get(key), str) and row[key].strip() for key in fields
            ):
                raise ValueError("Expected nonempty record fields")
            writer.writerow([row[key].strip() for key in fields])
        return buffer.getvalue()
Reasoning
Extend the one field list used by validation, header and row projection.
  • Hint 1: The same fields list drives validation and the header.
  • Hint 2: Do not build a second ad hoc comma-concatenation path.

Solution, tests and trade-offs

The solution extends the field list to three required string fields.

The solution extends the field list to three required string fields. The existing validation and writer loop then apply the same boundary to country and preserve the declared column order. Accepted examples include a comma, an embedded newline, Unicode text and an empty record population.

The tests compare complete CSV strings with a fixed newline convention. Malformed JSON and invalid record shapes must report a ValueError-family failure instead of being exported as plausible partial rows. A separate production ingestion boundary would also need size limits and source-specific validation policies.

Practice solution and acceptance checks

python

import csv
import io
import json

def convert(text):
    rows = json.loads(text)
    if not isinstance(rows, list):
        raise ValueError("Expected a list")
    fields = ["id", "title", "country"]
    with io.StringIO(newline="") as buffer:
        writer = csv.writer(buffer, lineterminator="\n")
        writer.writerow(fields)
        for row in rows:
            if not isinstance(row, dict) or not all(
                isinstance(row.get(key), str) and row[key].strip() for key in fields
            ):
                raise ValueError("Expected nonempty record fields")
            writer.writerow([row[key].strip() for key in fields])
        return buffer.getvalue()
Reasoning
Three-column exports preserve quoting and reject incomplete required records.

Interview reasoning and next step

In an interview, distinguish syntax parsing, structural validation, normalization and serialization.

In an interview, distinguish syntax parsing, structural validation, normalization and serialization. A parsed dictionary is not automatically a valid application record, and a quoted CSV field is not automatically safe under every consuming program's rules. Explain those boundaries using one concrete punctuation case.

The following lesson teaches standard-library tests that can actually detect a controlled wrong implementation. It brings together independent expected values, input preservation and the difference between a green test and a useful oracle.

Optional stretch — after the core task works: Round-trip the export with csv.DictReader and define a separate policy for formula-like spreadsheet cells.

Computer practice: https://applydjinn.com/typorithm/python-lab-20261105.zip. Run verify.py with your selected Python interpreter, then complete an exercise and check it with --practice followed by the lesson id. The lab uses only the standard library and disposable local fixtures; it never connects to the production database or company feeds.

Validate structured records and use the format writer instead of concatenating delimiters by hand.