Part 3 — Serialisation

ndict-tools provides built-in serialisation to JSON and pickle for all three public classes. Both formats support a full round-trip: the reconstructed object is an instance of the same class with the same default_setup.

Warning

Pickle files are unsafe when loaded from untrusted sources. Only unpickle files you created yourself or received from a trusted party. A UserWarning is always emitted on both to_pickle and from_pickle to remind you of this.

JSON round-trip

Use to_json() to write and from_json() to reconstruct:

from ndict_tools import NestedDictionary

nd = NestedDictionary({"project": {"name": "ndict-tools", "stars": 42}})
nd.to_json("/tmp/nd.json", indent=2)

nd2 = NestedDictionary.from_json(
    "/tmp/nd.json",
    default_setup={"indent": 2, "default_factory": NestedDictionary},
)
nd2[["project", "name"]]   # 'ndict-tools'

The default_setup parameter in from_json is required for the same reason as in from_dict() — the JSON file carries no information about the target class behaviour.

Non-string keys

JSON mandates string keys. ndict-tools encodes all non-string keys automatically using a reversible convention (DD-021) — you do not need to handle this yourself:

nd = NestedDictionary({42: {"label": "answer"}, "name": "demo"})
nd.to_json("/tmp/mixed.json")

nd2 = NestedDictionary.from_json(
    "/tmp/mixed.json",
    default_setup={"indent": 0, "default_factory": NestedDictionary},
)
nd2[42]["label"]   # 'answer' — integer key restored correctly

Supported key types for JSON encoding: str, int, float, bool, flat tuple, flat frozenset.

Pickle round-trip

Use to_pickle() and from_pickle():

from ndict_tools import NestedDictionary

nd = NestedDictionary({"a": {1: "one", 2: "two"}})
nd.to_pickle("/tmp/nd.pkl")
# Writes /tmp/nd.pkl and /tmp/nd.pkl.sha256 (SHA-256 integrity sidecar)

nd2 = NestedDictionary.from_pickle("/tmp/nd.pkl")
# Verifies the SHA-256 sidecar before unpickling
nd2["a"][1]   # 'one'

A .sha256 sidecar file is written alongside the pickle file. By default, from_pickle reads the sidecar and raises StackedValueError if the digest does not match or the sidecar is absent. To skip the check:

nd2 = NestedDictionary.from_pickle("/tmp/nd.pkl", verify=False)

Pickle preserves the full object state, including default_setup and default_factory, without any extra arguments on load.

JSON vs pickle — when to use which

JSON

Pickle

Key types

str, int, float, bool, flat tuple, flat frozenset

Any hashable type

Values

JSON-serialisable only (str, int, float, bool, list, dict, None)

Any picklable Python object

Interoperability

Any language, any tool

Python only

Readability

Human-readable text

Binary

Security

Safe to load from untrusted sources

Never load from untrusted sources

Integrity check

None built-in

SHA-256 sidecar (automatic)

``default_setup`` on load

Required

Preserved automatically

Rule of thumb: use JSON for configuration files, data exchange, and anything that crosses a trust boundary. Use pickle for local persistence of complex objects (non-string keys, custom value types) within a trusted environment.