We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Everyday Standard Library Tools step 31 of 55
Build a JSON Converter Other Code Can Extend
Write a converter that turns Python values into JSON-friendly ones. Build it
with singledispatch, so other code can add a type without editing it, and
test that with a Money class.
to_json_value([1, Decimal("0.25"), True]) # [1, "0.25", 1]
solve(["mixed:", "money:250", "mystery"], register_extra=True)
# [[1, "0.25", 1], {"cents": 250}, "TypeError"]
What to write
Make to_json_value(value: object) -> object a module-level
@singledispatch function whose base version raises TypeError. Register
these handlers:
| Type | Result |
|---|---|
int |
int(value), so True and False become 1 and 0 |
str |
The string unchanged |
Decimal |
str(value), which keeps formatting such as "1.50" |
datetime |
value.isoformat() |
list |
A new list with each item converted |
dict |
A new dict with str(key) keys and converted values. If two keys give the same string, the later value wins. |
Register list and dict by passing the class, as in
to_json_value.register(list, _list_to_json), and keep annotations such as
list[object] on those handlers.
Then write solve(specs: list[str], register_extra: bool) -> list[object].
Split each spec at its first colon into a kind and a payload, build the value
below, and convert it. Keep the input order. If the conversion raises
TypeError, append the string "TypeError" and go on.
| Spec | Value to build |
|---|---|
int:42 |
42 |
str:hello:world |
"hello:world" |
bool:true, bool:false |
True, False |
decimal:1.50 |
Decimal("1.50") |
datetime:<iso> |
datetime.fromisoformat(iso) |
list:1,2,3 |
[1, 2, 3] |
dict:a=1,b=2 |
{"a": 1, "b": 2} |
mixed: |
[1, Decimal("0.25"), True], whatever the payload |
money:250 |
A Money instance with cents == 250 |
| any other kind | A Mystery() instance |
Define Money, with a cents attribute, and Mystery inside solve, so that
each call makes fresh classes. When register_extra is true, register a
handler for Money that returns {"cents": value.cents}. Otherwise Money
has no handler and converts to "TypeError".
The tests check only the output, but the lesson is the dispatcher, so replace
the starter's isinstance chain.
Edge cases
- An empty
specslist gives[]. - A spec with no colon has an empty payload. Its kind is unknown, so it builds
a
Mystery. - Skip empty entries in
listanddictpayloads, solist:gives[]. Keys in adictpayload contain no comma or equals sign, and a repeated key keeps its last value. - Every payload for a known kind is valid, and a
boolpayload is alwaystrueorfalse. - A
Moneyhandler from an earlier call does not match the next call'sMoneyclass. The old handler stays in the registry, which would leak memory in a long-running service.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.