Metadata-Version: 2.1
Name: fromjson
Version: 0.1.0
Summary: Easily read objects from json
Home-page: https://git.sr.ht/~ilikeavocadoes/fromjson
License: Zlib
Author: Lassi Haasio
Requires-Python: >=3.8,<4.0
Classifier: License :: OSI Approved
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Project-URL: Repository, https://git.sr.ht/~ilikeavocadoes/fromjson
Description-Content-Type: text/markdown

# fromjson

Python library to easily read objects from JSON.

Have you ever had classes, which you want to read from JSON, but didn't want to write the boilerplate `from_json` classmethods to convert JSON to Python objects?
Now you don't have to!
fromjson adds a simple decorator for classes, which adds a `from_json` classmethod to the class.
It works on all classes, as long as the fields in the class are either Python primitive types (int, float, str, dict, list) or classes with a `from_json` classmethod, and the class has type hints for its constuctor.
This means that if you have nested classes which you want to load from JSON, you only need to add a decorator call for each of the classes.

This library is heavily inspired by the excellent Haskell library [aeson](https://hackage.haskell.org/package/aeson).

## Usage

All the examples have these imports
```
>>> from dataclasses import dataclass
>>> import json
>>>
>>> from fromjson import fromjson, tojson

```
Suppose we have the following JSON:

```{python}
>>> foo = """
... {
...   "field": "a string value",
...   "another_field": 42
... }
... """

```

We want to deserialise it into a class defined by:

```{python}
>>> @dataclass
... class Foo:
...     field: str
...     another_field: int

```

Adding a `@fromjson` decorator before the class definition adds a `from_json` classmethod to the class, which enables us to load it from a dictionary representation:

```
>>> @dataclass
... @fromjson
... class Foo:
...     field: str
...     another_field: int
...
>>> Foo.from_json(json.loads(foo))
Foo(field='a string value', another_field=42)

```

## `tojson`

For completeness, there's also a `@tojson` decorator, which does the opposite.
It is used similarly:

```{python}
>>> @dataclass
... @tojson
... class Foo:
...     field: str
...     another_field: int
...
>>> json.dumps(Foo("asd", 42).to_json())
'{"field": "asd", "another_field": 42}'

```

## Nested classes

The real power of fromjson is in deserialising user made classes, which have other user made classes as fields.
Suppose we have the following nested JSON data:

```
>>> bird = """
... {
...     "name": "Duck",
...     "cry": "Quack!",
...     "egg": {
...       "size": "Medium",
...       "color": "White"
...     }
... }
... """

```

We can parse it into the following classes, by adding a `@fromjson` call to each class:
```
>>> @dataclass
... @fromjson
... class Egg:
...     size: str
...     color: str
...
>>> @dataclass
... @fromjson
... class Bird:
...     name: str
...     cry: str
...     egg: Egg
...
>>> import json
>>> Bird.from_json(json.loads(bird))
Bird(name='Duck', cry='Quack!', egg=Egg(size='Medium', color='White'))

```

## Parsing logic

The json is parsed according to the following logic:
- The argument for `from_json` has to be a `Mapping` (i.e. an ordinary dictionary is fine)
- The type hints for the class are read
- For each type hint, if the type is one of `str`, `int`, `float`, `dict`, or `list`, the corresponding value from the argument mapping is used as-is.
- If the type is none of the enumerated primitive types, it has to be a class with a `from_json` classmethod, which is then used to parse the object.
- If the type isn't a primitive, and the class doesn't have a `from_json` classmethod, the parsing fails.

If your nested class has some really complicated parsing logic, for which `fromjson` is inadequate, you can write a `from_json` classmethod by hand.
Then the class can be used inside other classes, which have a `@fromjson` decorator call.


