Jsonable
coola.jsonable ¶
Utilities to convert nested data to JSON data.
to_jsonable turns any nested data into the values that
json.loads returns, with one function, one registry and one
transformer per type.
Properties:
- JSON data model: the output contains only
dictwithstrkeys,list,str,int,float,boolandNone. A subclass ofstr,intorfloat(anIntEnummember) is kept:jsonwrites it as its base value. - Accepted by
json: withon_unsupported="raise",json.dumps(to_jsonable(x))never raises, and the function is idempotent. - Strict and loud: a value whose type has no transformer raises a
TypeErrorthat names the type."warn"and"keep"are explicit opt-outs. - Type dispatch: the transformer is chosen from the type of the data, along the Method Resolution Order.
- Registry-extensible: a new type is supported by registering a
transformer; the plugin group is
coola.jsonable.transformers. - Keys are strings: a key is converted by the registry, then written
like
json.dumpswrites it. A key that cannot be a string raises aTypeErrorand two keys that give the same string raise aValueError. - Deterministic: a set becomes a sorted list, dictionaries keep their order, so the output is the same in every process.
- Pure: the input is never modified and the output shares no container with it.
- Cycle-safe: a container that contains itself raises a
ValueError. - Any depth: no
RecursionError, the traversal uses an explicit stack. - Consistent with siblings: dataclasses are entered, text and bytes are leaves, options are keyword-only.
Not guaranteed: a round trip to the Python types, strict JSON
(NaN and the infinities are kept), the sharing of a container
reachable by several paths (it is converted once per path).
Example
>>> from coola.jsonable import to_jsonable
>>> to_jsonable({"a": (1, 2), "b": {3, 1, 2}})
{'a': [1, 2], 'b': [1, 2, 3]}
coola.jsonable.ConvertTransformer ¶
Bases: InlineDisplayMixin, BaseTransformer[T]
Transformer that replaces the data by func(data), which is
converted in turn.
The result of func is converted by the engine like any other
value, until only JSON scalars and containers remain. A function
does not call the registry and does not convert its own result: it
only maps a value to a simpler one (a path to a str, an enum
member to its value, an array to a list).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable[[T], Any]
|
The function that maps the data to the value to convert. Its result must not be of the type of the data, because it would be converted by this transformer again, without end. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
From |
Example
>>> from pathlib import PurePosixPath
>>> from coola.jsonable import ConvertTransformer
>>> transformer = ConvertTransformer(str)
>>> children, rebuild = transformer.expand(PurePosixPath("a/b"))
>>> children
['a/b']
>>> rebuild(children)
'a/b'
coola.jsonable.DataclassTransformer ¶
Bases: InlineDisplayMixin, BaseTransformer[Any]
Transformer for dataclass instances that rebuilds a dict.
The values of the fields are the children, so they are converted
like the other values, and the converted values are collected in a
dict that maps the names of the fields to them. The fields are
read one by one: dataclasses.asdict is not used because it
deep-copies the values.
Example
>>> from dataclasses import dataclass
>>> from coola.jsonable import DataclassTransformer
>>> @dataclass
... class Point:
... x: int
... y: int
...
>>> children, rebuild = DataclassTransformer().expand(Point(1, 2))
>>> children
[1, 2]
>>> rebuild(["1", "2"])
{'x': '1', 'y': '2'}
coola.jsonable.MappingTransformer ¶
Bases: InlineDisplayMixin, BaseTransformer['Mapping[Any, Any]']
Transformer for mappings that rebuilds a dict with str
keys.
The keys and the values are the children, so a key is converted by
the registry like any other value (a Path gives a str). Then
a str key is kept, and a bool, int, float or
None key is written like json.dumps writes it ("true",
"1", "1.5", "null"). The order of the mapping is kept.
Raises:
| Type | Description |
|---|---|
TypeError
|
From the function that rebuilds the dictionary, if
a converted key is not a |
ValueError
|
From the function that rebuilds the dictionary, if two keys give the same string. |
Example
>>> from coola.jsonable import MappingTransformer
>>> transformer = MappingTransformer()
>>> children, rebuild = transformer.expand({"a": 1, 2: 3})
>>> children
['a', 2, 1, 3]
>>> rebuild(children)
{'a': 1, '2': 3}
coola.jsonable.SequenceTransformer ¶
Bases: InlineDisplayMixin, BaseTransformer['Sequence[Any]']
Transformer for sequences (list, tuple, deque,
range, ...) that rebuilds a list.
The elements are the children, so they are converted like the other values.
Example
>>> from coola.jsonable import SequenceTransformer
>>> transformer = SequenceTransformer()
>>> children, rebuild = transformer.expand((1, 2, 3))
>>> children
[1, 2, 3]
>>> rebuild(["1", "2", "3"])
['1', '2', '3']
coola.jsonable.SetTransformer ¶
Bases: InlineDisplayMixin, BaseTransformer['AbstractSet[Any]']
Transformer for sets that rebuilds a sorted list.
The elements are the children, so they are converted like the other
values, and the converted elements are collected in a list sorted
with _sorted_elements. The sort does not depend on the
iteration order of the set, so the output is the same in every
process.
Example
>>> from coola.jsonable import SetTransformer
>>> transformer = SetTransformer()
>>> children, rebuild = transformer.expand({3, 1, 2})
>>> rebuild([3, 1, 2])
[1, 2, 3]
coola.jsonable.get_default_registry ¶
get_default_registry() -> TransformerRegistry
Get or create the default global registry of the JSON transformers.
Returns a singleton registry that is pre-configured with the
transformers of the built-in containers, of the standard library
types and of the plugins of the group
coola.jsonable.transformers.
Returns:
| Type | Description |
|---|---|
TransformerRegistry
|
A singleton |
Notes
The singleton pattern means modifications to the returned
registry affect all future calls to this function. Use
registry.copy() for an isolated registry.
Example
>>> from coola.jsonable import get_default_registry
>>> get_default_registry().transform({"a": (1, 2)}, lambda x: x)
{'a': [1, 2]}
coola.jsonable.register_transformers ¶
register_transformers(
mapping: Mapping[type, Transformer[Any]],
*,
exist_ok: bool = False,
) -> None
Register custom transformers to the default global registry.
This function extends the singleton registry returned by
get_default_registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping
|
Mapping[type, Transformer[Any]]
|
Mapping of Python types to transformer instances. |
required |
exist_ok
|
bool
|
If |
False
|
Raises:
| Type | Description |
|---|---|
AlreadyRegisteredError
|
If any type is already registered and
|
Example
>>> from coola.jsonable import ConvertTransformer, register_transformers, to_jsonable
>>> class Point:
... def __init__(self, x, y):
... self.x, self.y = x, y
...
>>> register_transformers({Point: ConvertTransformer(lambda p: {"x": p.x, "y": p.y})})
>>> to_jsonable([Point(1, 2)])
[{'x': 1, 'y': 2}]
coola.jsonable.to_jsonable ¶
to_jsonable(
data: object,
*,
registry: TransformerRegistry | None = None,
on_unsupported: str = "raise",
) -> Any
Convert nested data to JSON data.
The output contains only dict with str keys, list,
str, int, float, bool and None: the values that
json.loads returns. Tuples, deques, sets and ranges become
lists (a set becomes a sorted list), mappings and dataclasses become
dictionaries. The keys are converted to strings like json.dumps
writes them. The conversion is one way and is not a round trip to
the Python types. NaN and the infinite floats are kept.
The options must be passed by keyword.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The data to convert. |
required |
registry
|
TransformerRegistry | None
|
The registry that finds the transformer of each type.
If |
None
|
on_unsupported
|
str
|
What happens to a value whose type has no
transformer. |
'raise'
|
Returns:
| Type | Description |
|---|---|
Any
|
The JSON data. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
If a value has no transformer (with
|
Example
>>> from pathlib import PurePosixPath
>>> from coola.jsonable import to_jsonable
>>> to_jsonable({"path": PurePosixPath("a/b"), 1: (1, 2), "s": {3, 1, 2}})
{'path': 'a/b', '1': [1, 2], 's': [1, 2, 3]}