Everyone Can Learn Program
中文 Start learning
Specialty · BackendLesson 2 of 6Free to read

JSON APIs and validation · Backend Development

What should an API do when the input is wrong?

Course contents

Why you need it

Anything can arrive at your server: typos, missing fields, text where a number should be, or someone deliberately trying to break it. An API that checks its input and explains every problem clearly is pleasant to build on; one that crashes or quietly stores junk causes bugs far away from the real cause.

The intuition

A request that creates something goes through the same steps every time:

  1. Parse: turn the JSON text into Python data with json.loads. If it isn't valid JSON → 400 {"error": "invalid JSON"}.
  2. Validate: check every field: present? right type? in range? Collect all the problems in a list, so the client can fix them in one go → 400 {"errors": [...]}.
  3. Act: store it.
  4. Respond: 201 with the new object, including the id the server gave it.

Validation is a plain function: data in, list of errors out. An empty list means valid.

Never trust the client: it might not be your app. Check on the server even if the web page checks too.

Definition

JSON body

The data a client sends with POST or PUT, as JSON text. json.loads turns it into dicts and lists.

Validation

Checking every field of the input before using it, and reporting what's wrong.

Examples

Text to data and back

Try it yourself

json.loads parses, json.dumps writes, and bad JSON raises an error you can catch.

import json

data = json.loads('{"name": "Ada", "tags": ["math", "code"]}')
print(data["tags"][0])
print(json.dumps({"ok": True, "count": 2}))

try:
    json.loads("{name: Ada}")
except json.JSONDecodeError as e:
    print("bad JSON:", e.msg)
Output
math
{"ok": true, "count": 2}
bad JSON: Expecting property name enclosed in double quotes
Step by step
  1. L3JSON objects become dicts, arrays become lists.
  2. L5Python True becomes JSON true.{"ok": true, "count": 2}
  3. L8Keys must be in double quotes in JSON, so this fails.

Parse, validate, create

Try it yourself

The full path of a create request.

import json

USERS = []

def validate(data):
    errors = []
    if not isinstance(data.get("name"), str) or not data.get("name"):
        errors.append("name is required")
    if not isinstance(data.get("age"), int):
        errors.append("age must be a whole number")
    return errors

def create_user(body):
    try:
        data = json.loads(body)
    except json.JSONDecodeError:
        return 400, {"error": "invalid JSON"}
    errors = validate(data)
    if errors:
        return 400, {"errors": errors}
    user = {"id": len(USERS) + 1, "name": data["name"], "age": data["age"]}
    USERS.append(user)
    return 201, user

print(create_user('{"name": "Ada", "age": 36}'))
print(create_user('{"name": "", "age": "36"}'))
print(create_user('not json'))
Output
(201, {'id': 1, 'name': 'Ada', 'age': 36})
(400, {'errors': ['name is required', 'age must be a whole number']})
(400, {'error': 'invalid JSON'})
Step by step
  1. L5Collect every problem, not just the first.
  2. L15Step 1: parse. Bad JSON stops here with 400.
  3. L18Step 2: validate.
  4. L22Step 3: store, with an id chosen by the server.
  5. L23Step 4: 201 and the new user.

Common mistakes

Stopping at the first error

If the client has three mistakes, reporting one at a time means three round trips. Collect them all.

Checking with `if data["age"]`

A missing key raises KeyError (a 500), and age 0 counts as false. Use data.get("age") and isinstance.

True is an int

In Python, isinstance(True, int) is True. If a field must be a real number, also reject bools.

Quick quiz

1. A client sends `{"qty": "three"}`, but qty must be a number. Which response fits?

  1. A400 with a message saying qty must be a number
  2. B500 Internal Server Error
  3. C200 and store it anyway
Show the answer

Answer: A

The client made a mistake it can fix: 4xx, with a helpful message.

2. What does this print?

import json
print(json.loads('[1, "2", true, null]'))
  1. A
    [1, '2', True, None]
  2. B
    [1, 2, True, None]
  3. C
    [1, '2', 'true', 'null']
Show the answer

Answer: A

JSON true and null become Python True and None; "2" stays a string.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictThree requests

    Type exactly what this prints.

  2. ModifyA stricter age check

    validate lets bad ages through: -5, 300 and True all pass. Change it so age must be an int (not a bool) from 0 to 150, with the error "age must be a whole number from 0 to 150". Keep the name check.

  3. WriteCreate an order

    Write create_order(body), where body is JSON text:

    • not valid JSON → (400, {"error": "invalid JSON"})
    • otherwise collect errors, in this order:

    - "item is required" unless item is a non-empty string
    - "qty must be from 1 to 99" unless qty is an int (not a bool) from 1 to 99

    • any errors → (400, {"errors": [...]})
    • valid → append {"id": ..., "item": ..., "qty": ...} to ORDERS (ids start at 1) and return (201, order)

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
json modulejson 模块json.loads turns JSON text into Python data; json.dumps does the reverse.
Validation校验Checking input before trusting it.