Skip to main content

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 functions — validate_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.

Metadata

Release files for json-data-types 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for json-data-types 0.2.0
File Size Uploaded Provenance Actions
json_data_types-0.2.0.tar.gz 15.7 kB With provenance Details for json_data_types-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for json-data-types 0.2.0
File Interpreter ABI Platform Actions
json_data_types-0.2.0-py3-none-any.whl Python 3 none any Details for json_data_types-0.2.0-py3-none-any.whl

Total release size: 26.1 kB

Release files / json_data_types-0.2.0.tar.gz

Download URL json_data_types-0.2.0.tar.gz
Size 15.7 kB
Tags Source
SHA-256 checksum
How to use checksums
479ecfd81358ea704980b1faf579aef452afb09212dc9f636b068eaf4a426529
BLAKE2b-256 checksum
How to use checksums
045f95640f9ef57cc510918851b2fe1100365614a0b838f4945451e7e099a51e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 26, 2026.

Transparency log

Release files / json_data_types-0.2.0-py3-none-any.whl

Download URL json_data_types-0.2.0-py3-none-any.whl
Size 10.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
65fb77313999287a3bea1a56988acf3c82b30e3bb81265979ad6428c63eb28ae
BLAKE2b-256 checksum
How to use checksums
57d8717e3d5db4cfaa689420bf50dee89a4e97b04b0215776556b07d9e961350
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 26, 2026.

Transparency log

Security for json-data-types 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for json_data_types-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page