Batu Lab NotesPractical developer guides

Convert a JSON timestamp field only after a type check

By Batu ยท English technical notes

Also published in our primary archive.

To convert a JSON timestamp field only after a type check, call json.loads(), reject values that are not strings, and invoke datetime.fromisoformat() only for the accepted string branch. The unchecked half of this in-memory experiment shows why order matters: JSON 1700000000 decodes to int and null to None, so passing either directly to fromisoformat() raises TypeError. Those errors report a parser argument mismatch, rather than the useful contract decision that an epoch or missing timestamp is unsupported.

The exact JSON text and its decoded Python list are printed first. The fixture then deliberately catches and prints the exception type for every unchecked attempt. The guarded conversion returns explicit type results for the numeric and null values. Its call counter verifies that only the one documented str value reaches datetime parsing. The successful result is normalized to a UTC ISO string, and assertions check both rejected boundaries, the output list, and the one guarded parser call.

This example requires Python 3.11+ because datetime.fromisoformat() accepts the trailing Z form used here. The json module maps JSON numbers, strings, and null to int, str, and None by default. If a producer intentionally supports numeric epochs, add a separate, documented branch that specifies units and timezone semantics; this narrow contract intentionally does not infer them.

AI assistance disclosure: this article was drafted with AI using an in-memory synthetic fixture.

Sources: Python json documentation and Python datetime documentation.

import json
from datetime import datetime, timezone

text = '[1700000000, "2024-01-01T00:00:00Z", null]'
values = json.loads(text)


def unchecked_result(value):
    try:
        datetime.fromisoformat(value)
    except TypeError as error:
        return f"unchecked:{type(value).__name__}:{type(error).__name__}"
    return f"unchecked:{type(value).__name__}:accepted"


parse_calls = 0


def convert_documented_timestamp(value):
    global parse_calls
    if not isinstance(value, str):
        return f"unsupported_type:{type(value).__name__}"
    parse_calls += 1
    parsed = datetime.fromisoformat(value)
    assert parsed.tzinfo is not None
    return parsed.astimezone(timezone.utc).isoformat()


unchecked = [unchecked_result(value) for value in values]
converted = [convert_documented_timestamp(value) for value in values]

print(text)
print(values)
for result in unchecked:
    print(result)
for result in converted:
    print(result)
print(f"guarded_parser_calls:{parse_calls}")

assert unchecked[0] == "unchecked:int:TypeError"
assert unchecked[2] == "unchecked:NoneType:TypeError"
assert converted == [
    "unsupported_type:int",
    "2024-01-01T00:00:00+00:00",
    "unsupported_type:NoneType",
]
assert parse_calls == 1
[1700000000, "2024-01-01T00:00:00Z", null]
[1700000000, '2024-01-01T00:00:00Z', None]
unchecked:int:TypeError
unchecked:str:accepted
unchecked:NoneType:TypeError
unsupported_type:int
2024-01-01T00:00:00+00:00
unsupported_type:NoneType
guarded_parser_calls:1