Metadata-Version: 2.4
Name: stablejson
Version: 0.1.1
Summary: Deterministic JSON serialization and SHA-256 hashing for Python.
Author: SaltyDiff
License-Expression: Apache-2.0
Project-URL: Source, https://github.com/SaltyDiff/stablejson
Project-URL: Issues, https://github.com/SaltyDiff/stablejson/issues
Project-URL: Changelog, https://github.com/SaltyDiff/stablejson/releases
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Dynamic: license-file

# stablejson

Deterministic JSON serialization and SHA-256 hashing for Python.

stablejson turns supported JSON-compatible input into a deterministic compact
JSON string and computes SHA-256 over its UTF-8 bytes.

Identical JSON-compatible values should produce identical digests across
processes and machines. That makes content addressing, cache keys, and
integrity checks reliable when ordinary `json.dumps` would still vary by key
order or formatting.

## Installation

Requires Python 3.12+.

```bash
pip install stablejson
```

Releases are published from GitHub to PyPI using Trusted Publishing. No PyPI
API token is stored in this repository.

For local development from a checkout:

```bash
pip install .
```

## Quick start

### Default value mode

```python
from stablejson import canonicalize

result = canonicalize({"b": 1, "a": 2})
print(result)
```

`input_kind` defaults to `"value"`, so Python objects are canonicalized directly.

Representative success response:

```python
{
    "ok": True,
    "canonical": '{"a":2,"b":1}',
    "sha256": "d3626ac30a87e6f7a6428233b3c68299976865fa5508e4267c5415c76af7a772",
    "observed_input_kind": "value",
}
```

### Document mode

```python
from stablejson import canonicalize

result = canonicalize('{"b": 1, "a": 2}', input_kind="document")
print(result["canonical"], result["sha256"])
```

With `input_kind="document"`, `content` must be a Unicode string containing one
JSON value. The parsed value is then canonicalized with the same rules as value
mode.

Both modes produce the same canonical text and digest for equivalent JSON data.

### Failure response

Unsupported values fail closed. For example, floats are rejected:

```python
canonicalize(1.0)
```

Representative failure response:

```python
{
    "ok": False,
    "canonical": None,
    "sha256": None,
    "failure": {
        "code": "UNSUPPORTED_VALUE",
        "message": "unsupported value",
    },
}
```

## Public API

```python
canonicalize(content, *, input_kind="value") -> dict
```

| Argument | Meaning |
| --- | --- |
| `content` | JSON-compatible Python value, or a JSON text string when `input_kind="document"` |
| `input_kind` | `"value"` (default) or `"document"` |

Invalid `input_kind` values fail closed with `INVALID_REQUEST`.

Also exported: `INVALID_REQUEST`, `MALFORMED_JSON`, `UNSUPPORTED_VALUE`,
`FAILURE_MESSAGES`, and `__version__`.

stablejson uses a versioned internal engine for compatibility with its original
implementation. Ordinary callers do not pass engine identifiers.

### Accepted value types (`input_kind="value"`)

- `None`
- `bool`
- `int`
- `str`
- `list` of accepted values
- `dict` with string keys and accepted values

### Rejected values

- floats, including finite floats such as `1.0`
- `NaN` / `Infinity` in Python values or JSON documents
- `bytes` / `bytearray`
- non-string mapping keys
- tuples, sets, custom objects, and other non-JSON-compatible types
- malformed JSON documents (`MALFORMED_JSON`)
- nesting deeper than 128 (`UNSUPPORTED_VALUE`)
- invalid `input_kind` (`INVALID_REQUEST`)

## Deterministic behavior

Serialization rules:

- object keys are sorted lexicographically
- separators are compact: `,` and `:` with no spaces
- Unicode is preserved (`ensure_ascii=False`)
- non-finite numbers are never emitted (`allow_nan=False`)

SHA-256 behavior:

- digest is SHA-256 over the UTF-8 bytes of the canonical string
- digest is returned as a lowercase hex string in `sha256`
- there is no algorithm prefix and no multibase encoding

Depth limit:

- structures may nest up to depth 128
- depth 129 and deeper are rejected with `UNSUPPORTED_VALUE`

`canonicalize` does not mutate the caller-supplied content.

## Security and limitations

- Pure computation only: no network, filesystem, subprocess, or other I/O
- Fail-closed typed errors instead of partial canonical output
- Callers remain responsible for bounding untrusted input size; this library
  enforces nesting depth, not byte length or wall-clock budgets
- This is **not** [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) JSON
  Canonicalization Scheme (JCS), and it does not claim JCS or RFC 8785
  compatibility
- Floats are intentionally unsupported to keep this package's determinism
  contract narrow and exact. That is a product boundary, not a claim that
  floating-point JSON can never be serialized deterministically.

## Development

From a repository checkout:

```bash
python -m pip install -e ".[dev]"
pytest -q
ruff check src tests
python -m pip install build
python -m build
```

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).
