Metadata-Version: 2.1
Name: jsons
Version: 0.3.3
Summary: For serializing Python objects to JSON and back
Home-page: https://github.com/ramonhagenaars/jsons
Author: Ramon Hagenaars
License: UNKNOWN
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown

[![PyPI version](https://badge.fury.io/py/jsons.svg)](https://badge.fury.io/py/jsons)
[![Build Status](https://travis-ci.org/ramonhagenaars/geomodels.svg?branch=master)](https://travis-ci.org/ramonhagenaars/jsons)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ramonhagenaars/jsons/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ramonhagenaars/jsons/?branch=master)
[![Maintainability](https://api.codeclimate.com/v1/badges/17d997068b3387c2f2c3/maintainability)](https://codeclimate.com/github/ramonhagenaars/jsons/maintainability)


# jsons
A Python (3.5+) lib for deeply serializing Python objects to dicts or strings and for 
deserializing dicts or strings to Python objects using type hints.

With `jsons`, you can serialize/deserialize most objects already. You can also easily
extend `jsons` yourself by defining a custom serializer/deserializer for a certain type.
Furthermore, any default serializer/deserializer can be overridden. Some 
serializers/deserializers accept extra parameters to allow you to tune the 
serialization/deserialization process to your need.

`jsons` generates human-readable dicts or JSON strings that are not polluted with
metadata. 

##### Why not use `__dict__` for serialization?
* The `__dict__` attribute only creates a *shallow* dict of an instance. Any contained 
  object is not serialized to a dict.
* The `__dict__` does not take `@property` methods in account.
* Not all objects have a `__dict__` attribute (e.g. `datetime` does not).
* The serialization process of `__dict__` cannot easily be tuned.
* There is no means to deserialize with `__dict__`.

## Installation
```
pip install jsons
```

## Usage
```
import jsons


some_instance = jsons.load(some_dict, SomeClass)  # Deserialization
some_dict = jsons.dump(some_instance)  # Serialization
```

## API overview
* ``dump(obj: object) -> dict``: serializes an object to a dict.
* ``load(json_obj: dict, cls: type = None) -> object``: deserializes a dict to an object of type ``cls``.
* ``dumps(obj: object, *args, **kwargs) -> str``: serializes an object to a string.
* ``loads(s: str, cls: type = None, *args, **kwargs) -> object`` deserializes a string to an object of type ``cls``.
* ``set_serializer(c: callable, cls: type) -> None``: sets a custom serialization function for type ``cls``.
* ``set_deserializer(c: callable, cls: type) -> None``: sets a custom deserialization function for type ``cls``.
* ``JsonSerializable``: a base class that allows for convenient use of the jsons features.

## Example with dataclasses
```
from dataclasses import dataclass
from typing import List
import jsons


# You can use dataclasses (since Python3.7). Regular Python classes (Python3.5+) will work as well as long as 
# type hints are present for custom classes.
@dataclass
class Student:
    name: str


@dataclass
class ClassRoom:
    students: List[Student]


c = ClassRoom([Student('John'), Student('Mary'), Student('Greg'), Student('Susan')])
dumped_c = jsons.dump(c)
print(dumped_c)
# Prints:
# {'students': [{'name': 'John'}, {'name': 'Mary'}, {'name': 'Greg'}, {'name': 'Susan'}]}
loaded_c = jsons.load(dumped_c, ClassRoom)
print(loaded_c)
# Prints:
# ClassRoom(students=[Student(name='John'), Student(name='Mary'), Student(name='Greg'), Student(name='Susan')])

```

## Example with regular classes
```
from typing import List
import jsons


class Student:
    # Since ``name`` is expected to be a string, no type hint is required.
    def __init__(self, name):
        self.name = name


class ClassRoom:
    # Since ``Student`` is a custom class, a type hint must be given.
    def __init__(self, students: List[Student]):
        self.students = students


c = ClassRoom([Student('John'), Student('Mary'), Student('Greg'), Student('Susan')])
dumped_c = jsons.dump(c)
print(dumped_c)
# Prints:
# {'students': [{'name': 'John'}, {'name': 'Mary'}, {'name': 'Greg'}, {'name': 'Susan'}]}
loaded_c = jsons.load(dumped_c, ClassRoom)
print(loaded_c)
# Prints:
# <__main__.ClassRoom object at 0x0337F9B0>

```
## Example with JsonSerializable
```
from jsons import JsonSerializable


class Car(JsonSerializable):
    def __init__(self, color):
        self.color = color

c = Car('red')
cj = c.json  # You can also do 'c.dump(**kwargs)'
print(cj)
# Prints:
# {'color': 'red'}
c2 = Car.from_json(cj)  # You can also do 'Car.load(cj, **kwargs)'
print(c2.color)
# Prints:
# 'red'
```

## Advanced features

### Overriding the default (de)serialization behavior
You may alter the behavior of the serialization and deserialization processes yourself by defining your own
custom serialization/deserialization functions.

```
jsons.set_serializer(custom_serializer, datetime)  # A custom datetime serializer.
jsons.set_deserializer(custom_deserializer, str)  # A custom string deserializer.
```

### Transforming the JSON keys
You can have the keys transformed by the serialization or deserialization process by providing a transformer 
function that takes a string and returns a string.

```
result = jsons.dump(some_obj, key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE)
# result could be something like: {'thisIsTransformed': 123}

result = jsons.load(some_dict, SomeClass, key_transformer=jsons.KEY_TRANSFORMER_SNAKECASE)
# result could be something like: {'this_is_transformed': 123}
```

The following casing styles are supported:

```
KEY_TRANSFORMER_SNAKECASE   # snake_case
KEY_TRANSFORMER_CAMELCASE   # camelCase
KEY_TRANSFORMER_PASCALCASE  # PascalCase
KEY_TRANSFORMER_LISPCASE    # lisp-case
```

### Customizing JsonSerializable
If you're using jsons to (de)serialize on multiple locations in your code using 
the same ``kwargs`` every time, you might want to use the `JsonSerializable` 
class. You can extract a dynamic class from `JsonSerializable` with the 
serializing and deserializing methods (`dump`, `load`, ...) overridden, to make
them behave as if these methods are called with your ``kwargs``.

```
custom_serializable = JsonSerializable\
    .with_dump(key_transformer=KEY_TRANSFORMER_CAMELCASE)\
    .with_load(key_transformer=KEY_TRANSFORMER_SNAKECASE)

class Person(custom_serializable):
    def __init__(self, my_name):
        self.my_name = my_name

p = Person('John')
p.json  # {'myName': 'John'}  <-- note the camelCase

p2 = Person.from_json({'myName': 'Mary'})
p2.my_name  # 'Mary'  <-- note the snake_case in my_name
```


