Skip to main content

Type hinting and validation for JSON-serializable Python data types.

Project description

json-data-types

CI PyPI version Python versions License: MIT

json-data-types provides type aliases, validators, and a flexible object-oriented validation framework for Python values that are directly serializable to JSON — scalars, dicts, and lists, in both strict (dict/list) and duck-typed (Mapping/Sequence) flavors.

Highlights

  • JsonData, JsonDict, JsonList — strict type aliases requiring real dict and list objects.
  • JsonDataLike, JsonDictLike, JsonListLike — duck-typed aliases accepting any Mapping or Sequence.
  • JsonScalar — the union of the five JSON primitive types (int, float, str, bool, None).
  • JsonScalarTypes — a runtime tuple[type, ...] constant suitable for isinstance() checks.
  • Validator functionsvalidate_json_* and loads_json_* for each type alias, with deep or shallow validation and cyclic-reference detection.
  • JsonObjectValidator — an extensible base class for building custom validators via subclassing.
  • NotJsonDataError — a ValueError subclass raised on validation failure.
  • Zero dependencies — pure Python, no third-party packages required.
  • Fully typed — PEP 561 compliant, inline annotations, works with mypy strict mode.

Installation

pip install json-data-types

Type Hierarchy

JsonScalar       int | float | str | bool | None

JsonDataLike     JsonScalar | JsonListLike | JsonDictLike
  JsonListLike   Sequence[JsonDataLike]          # any Sequence (not str/bytes)
  JsonDictLike   Mapping[str, JsonDataLike]      # any Mapping with str keys

JsonData         JsonScalar | JsonList | JsonDict
  JsonList       list[JsonData]                  # real list only
  JsonDict       dict[str, JsonData]             # real dict only

Use the *Like variants when accepting data that may come from custom Mapping or Sequence implementations (e.g., collections.OrderedDict, types.MappingProxyType, tuple). Use the strict variants (JsonData, JsonDict, JsonList) when you need plain Python dict and list objects — the most common case when working with json.loads() output.

Quick Start

from json_data_types import JsonData, JsonDict, validate_json_data

# Annotate function parameters and return types
def process(payload: JsonData) -> JsonDict:
    assert isinstance(payload, dict)
    return {"status": "ok", "echo": payload}

# Validate and typecast untrusted data at a boundary
raw: object = {"key": [1, True, None, "hello"]}
data: JsonData = validate_json_data(raw)   # raises NotJsonDataError if invalid

Type Aliases

JsonScalar

JsonScalar: TypeAlias = int | float | str | bool | None

Any JSON primitive value. Maps to JSON's number (integers and floats), string, boolean, and null.

JsonScalarTypes

JsonScalarTypes: Final[tuple[type, ...]] = (int, float, str, bool, NoneType)

A runtime constant holding the same types as JsonScalar, usable with isinstance():

from json_data_types import JsonScalarTypes

isinstance(42, JsonScalarTypes)    # True
isinstance([1, 2], JsonScalarTypes)  # False

JsonData

JsonData: TypeAlias = JsonScalar | JsonList | JsonDict

Any fully JSON-serializable Python value built exclusively from dict, list, and the scalar types. This is the type you get back from json.loads().

JsonDict

JsonDict: TypeAlias = dict[str, JsonData]

A dict mapping str keys to JsonData values.

JsonList

JsonList: TypeAlias = list[JsonData]

A list of JsonData values.

JsonDataLike

JsonDataLike: TypeAlias = JsonScalar | JsonListLike | JsonDictLike

Like JsonData but accepts any Mapping or Sequence rather than only dict and list. Useful at API boundaries where callers may pass OrderedDict, MappingProxyType, named tuples, etc.

JsonDictLike

JsonDictLike: TypeAlias = Mapping[str, JsonDataLike]

Any Mapping with str keys and JsonDataLike values.

JsonListLike

JsonListLike: TypeAlias = Sequence[JsonDataLike]

Any Sequence of JsonDataLike values. str is excluded — it is treated as a scalar (JsonScalar), not as a sequence of characters.

NotJsonDataError

All validators raise NotJsonDataError on failure. It is a subclass of ValueError, so existing except ValueError clauses continue to work.

from json_data_types import NotJsonDataError, validate_json_data

try:
    validate_json_data((1, 2, 3))
except NotJsonDataError as e:
    print(e)  # Invalid top-level JSON data: (1, 2, 3) ...

Validator Functions

validate_json_data(data, fast=False) -> JsonData

Validates that data is a pure-Python JSON-serializable value. Raises NotJsonDataError if any value is not of an allowed type, if any dict key is not a str, or if a cyclic reference is detected.

from json_data_types import validate_json_data

data = validate_json_data({"users": [{"id": 1, "score": 9.5, "active": True}]})
# data is typed as JsonData

With fast=True, only the top-level type is checked — no recursion, no cycle detection. Useful when you trust the structure but need a quick typecast.

data = validate_json_data(payload, fast=True)  # shallow check only

validate_json_dict(data, fast=False) -> JsonDict

Validates that data is a dict with str keys and JsonData values.

cfg: JsonDict = validate_json_dict(raw_config)

validate_json_list(data, fast=False) -> JsonList

Validates that data is a list of JsonData values.

items: JsonList = validate_json_list(raw_items)

validate_json_data_like(data, fast=False) -> JsonDataLike

Like validate_json_data but accepts any Mapping or Sequence rather than only dict and list.

from collections import OrderedDict
from json_data_types import validate_json_data_like

od = OrderedDict([("a", 1), ("b", [2, 3])])
result = validate_json_data_like(od)  # passes

validate_json_dict_like(data, fast=False) -> JsonDictLike

Validates that data is a Mapping with str keys and JsonDataLike values. Accepts any Mapping subtype at all nesting levels.

from types import MappingProxyType
from json_data_types import validate_json_dict_like

mp = MappingProxyType({"key": [1, 2, 3]})
result = validate_json_dict_like(mp)  # passes

validate_json_list_like(data, fast=False) -> JsonListLike

Validates that data is a Sequence of JsonDataLike values. str is excluded. Accepts any Sequence subtype (including tuple) at all nesting levels.

from json_data_types import validate_json_list_like

result = validate_json_list_like((1, True, None, {"a": 1}))  # passes

loads_json_* Functions

Each validator has a corresponding loads_json_* function that combines json.loads() with validation, returning the result cast to the appropriate type:

Function Returns
loads_json_data(s, *, fast=False, **kwargs) JsonData
loads_json_dict(s, *, fast=False, **kwargs) JsonDict
loads_json_list(s, *, fast=False, **kwargs) JsonList
loads_json_data_like(s, *, fast=False, **kwargs) JsonDataLike
loads_json_dict_like(s, *, fast=False, **kwargs) JsonDictLike
loads_json_list_like(s, *, fast=False, **kwargs) JsonListLike

s may be str, bytes, or bytearray. Extra keyword arguments are forwarded to json.loads().

from json_data_types import loads_json_dict

config: JsonDict = loads_json_dict(response.content)

Object-Oriented Validator API

Each validator is an instance of a concrete subclass of JsonObjectValidator. The singleton instances used by the module-level functions are:

Instance Class
(used by validate_json_data) JsonDataValidator
(used by validate_json_dict) JsonDictValidator
(used by validate_json_list) JsonListValidator
(used by validate_json_data_like) JsonDataLikeValidator
(used by validate_json_dict_like) JsonDictLikeValidator
(used by validate_json_list_like) JsonListLikeValidator

Validator instances are callable (__call__) and have a loads() method, both with the same signature as the module-level functions. You can create your own instances directly:

from json_data_types import JsonDataValidator, NotJsonDataError

v = JsonDataValidator()
data = v(raw)          # same as validate_json_data(raw)
data = v.loads(text)   # same as loads_json_data(text)

Subclassing JsonObjectValidator

JsonObjectValidator[_T] is a generic base class. Override class variables to customize which types are accepted at each level:

Class variable Default Purpose
_scalar_types JsonScalarTypes Types treated as scalars
_dict_types (dict,) Types treated as JSON objects
_list_types (list,) Types treated as JSON arrays
_dict_key_types (str,) Allowed dict key types
_toplevel_types None (same as allowed) Restrict the top-level type
_excluded_types (bytes, bytearray) Always-excluded types
_excluded_scalar_types () Excluded from scalar check
_excluded_dict_types () Excluded from dict check
_excluded_list_types (str, bytes, bytearray) Excluded from list check
_excluded_toplevel_types () Excluded at top level only

Example — a validator that only accepts dict at the top level but uses duck-typed Mapping/Sequence checks internally:

from collections.abc import Mapping, Sequence
from json_data_types import JsonObjectValidator, JsonDictLike

class StrictTopDuckDeepValidator(JsonObjectValidator[JsonDictLike]):
    _toplevel_types = (dict,)      # top level must be a real dict
    _dict_types = (Mapping,)       # nested dicts may be any Mapping
    _list_types = (Sequence,)      # nested lists may be any Sequence

v = StrictTopDuckDeepValidator()

Common Patterns

Validating API input

from json_data_types import JsonDict, loads_json_dict

def handle_request(body: bytes) -> JsonDict:
    return loads_json_dict(body)   # parses + validates in one call

Accepting flexible input

from json_data_types import JsonDataLike, validate_json_data_like
import json

def serialize(value: JsonDataLike) -> str:
    validate_json_data_like(value)   # raises NotJsonDataError if invalid
    return json.dumps(value)

Detecting cyclic structures before serialization

from json_data_types import validate_json_data

bad: dict = {}
bad["self"] = bad   # cyclic!

validate_json_data(bad)
# NotJsonDataError: Cyclic reference detected in JsonDataLike object

Using fast=True for trusted data

from json_data_types import JsonData, validate_json_data

# Already validated upstream; just need the typecast
data: JsonData = validate_json_data(trusted_payload, fast=True)

Checking scalar types at runtime

from json_data_types import JsonScalarTypes

def is_json_scalar(v: object) -> bool:
    return isinstance(v, JsonScalarTypes)

Supported Python Versions

Python 3.10 through 3.14.

License

MIT. See LICENSE.


For development and release workflow documentation, see CONTRIBUTING.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

json_data_types-0.2.0.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

json_data_types-0.2.0-py3-none-any.whl (10.3 kB view details)

Uploaded Python 3

File details

Details for the file json_data_types-0.2.0.tar.gz.

File metadata

  • Download URL: json_data_types-0.2.0.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for json_data_types-0.2.0.tar.gz
Algorithm Hash digest
SHA256 479ecfd81358ea704980b1faf579aef452afb09212dc9f636b068eaf4a426529
MD5 d859cdb5cf221e867d49b1dbf8e07379
BLAKE2b-256 045f95640f9ef57cc510918851b2fe1100365614a0b838f4945451e7e099a51e

See more details on using hashes here.

Provenance

The following attestation bundles were made for json_data_types-0.2.0.tar.gz:

Publisher: publish.yml on mckelvie-org/json-data-types

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file json_data_types-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: json_data_types-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 10.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for json_data_types-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 65fb77313999287a3bea1a56988acf3c82b30e3bb81265979ad6428c63eb28ae
MD5 ed939ff7f939cf160e2a38fb4c3d3b49
BLAKE2b-256 57d8717e3d5db4cfaa689420bf50dee89a4e97b04b0215776556b07d9e961350

See more details on using hashes here.

Provenance

The following attestation bundles were made for json_data_types-0.2.0-py3-none-any.whl:

Publisher: publish.yml on mckelvie-org/json-data-types

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page