README.md 3.69 KB
Newer Older
Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
1
# Dictionary deserializer
Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
2 3 4 5 6

Dictionary deserializer is a project built to convert dictionaries into
composite classes in an intuitive way. Special attention was also paid
to being friendly to static type-checkers and IDE autocompletes.

Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
It is expected that this library is used together with a JSON-to-dict
deserializer like `json.loads`.

## Design

This project was originally meant as a proof of concept, to be used to find
other projects that would be able to replace this, with the required feature
set. That project was not found, and therefore, this project was expanded.

### Requirements

* Use type hints for type validation
* Allow polymorphism
    * Through `typing.Union`s
    * Through subclassing
* Support a large part of the `typing` module's types
* Allow validations on values
* Be able to validate and deserialize _any_ compliant JSON structure
* Be compatible with static type checkers and IDE hinting
* Have a small impact on existing code starting to use this library

## Examples

None of this code is actually useful if you don't understand how to use it. It
is very simple. Here are some examples:

### Specifying a structure

```python
from typing import Optional

from dict_deserializer.deserializer import Deserializable

class User(Deserializable):
    email: str                  # Type must be a string
    username: str               # Type must be a string
    password: Optional[str]     # Type must either be a string or a None
```

### Deserialization

```python
from dict_deserializer.deserializer import deserialize, Rule

# Successful
deserialize(Rule(User), {
    'email': 'pypi@rolfvankleef.nl',
    'username': 'rkleef',
})

# Fails because optional type is wrong
deserialize(Rule(User), {
    'email': 'pypi@rolfvankleef.nl',
    'username': 'rkleef',
    'password': 9.78,
})
```

### Polymorphic structures
```python
from typing import Optional, Any, List

from dict_deserializer.deserializer import Deserializable
from dict_deserializer.annotations import abstract

@abstract
class DirectoryObject(Deserializable):
    name: str
    meta: Any

class User(DirectoryObject):
    full_name: str
    first_name: Optional[str]

class Group(DirectoryObject):
    members: List[DirectoryObject]
```

If you deserialize into `Rule(DirectoryObject)`, the matching class will
automatically be selected. If none of the subclasses match, an error is thrown
since the DirectoryObject is declared abstract.

If you want to discriminate not by field names or types, but by their values,
one can choose to define a `@discriminator` annotation.

### Value validations

The syntax for validating the value of a key is currently a bit weird. It is
incompatible with existing syntax for defaults, but the type syntax is the same.

Example:

```python
from typing import Optional

from dict_deserializer.deserializer import Deserializable
from dict_deserializer.annotations import validated

class Test(Deserializable):
    name: Optional[str]
    
    @validated(default='Unknown')
    def name(self, value):
        if len(value) > 20:
            raise TypeError('Name may not be longer than 20 characters.')

```

Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
115 116 117 118 119 120 121
## Limitations

This library uses the `typing` module extensively. It does, however, only
support some of its types. This is a list of verified composite types:

* `Union` (Including `Optional`)
* `List`
122
* `Tuple`
Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
123
* `Any`
Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
124 125 126
* `dict_deserializer.deserializer.Deserializable`
* `dict`
* `list`
Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
127 128 129 130 131 132 133 134

It supports these types as terminal types:

* `int`
* `float`
* `str`
* `NoneType`
* `bool`
Rolf H. B. van Kleef's avatar
Rolf H. B. van Kleef committed
135 136 137 138 139 140 141 142 143 144

## Planned features

* NamedTuples
    * The anonymous namedtuple and the class-namedtuples with (optionally) type annotations.
* Dataclasses
* A way to allow deserializing into a class not extending `Deserializable`
* Enums
* Sets
    * From lists