Metadata-Version: 2.1
Name: jsons
Version: 0.5.3
Summary: For serializing Python objects to JSON (dicts) and back
Home-page: https://github.com/ramonhagenaars/jsons
Author: Ramon Hagenaars
Author-email: ramon.hagenaars@gmail.com
License: UNKNOWN
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Description-Content-Type: text/x-rst

|PyPI version| |Docs| |Build Status| |Scrutinizer Code Quality|
|Maintainability|

jsons
=====

A Python (3.5+) lib for easily and 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
'''''

.. code:: python

   import jsons

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

In some cases, you have instances that contain other instances that need
(de)serialization, for instance with lists or dicts. You can use the
``typing`` classes for this as is demonstrated below.

.. code:: python

   from typing import List, Tuple
   import jsons

   # For more complex deserialization with generic types, use the typing module
   list_of_tuples = jsons.load(some_dict, List[Tuple[AClass, AnotherClass]])

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 JSON string.
-  ``loads(s: str, cls: type = None, *args, **kwargs) -> object``:
   deserializes a JSON string to an object of type ``cls``.
-  ``dumpb(obj: object, encoding: str = 'utf-8', *args, **kwargs) -> bytes``:
   serializes an object to bytes.
-  ``loadb(bytes_: bytes, cls: type = None, encoding: str = 'utf-8', *args, **kwargs)``:
   deserializes bytes 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.
-  ``decorators.loaded``: a decorator that will load all parameters before
   entering the function/method body and the return value upon returning.
-  ``decorators.dumped``: a decorator that will dump all parameters before
   entering the function/method body and the return value upon returning.

Examples
''''''''

Example with dataclasses
------------------------

.. code:: python

   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
----------------------------

.. code:: python

   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
-----------------------------

.. code:: python

   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
'''''''''''''''''

Using decorators
----------------

You can decorate a function or method with ``@loaded()`` or ``@dumped()``,
which will respectively load or dump all parameters and the return value.

.. code:: python

   from datetime import datetime
   from jsons.decorators import loaded


   @loaded()
   def some_func(x: datetime) -> datetime:
       # x is now of type datetime.
       return '2018-10-07T19:05:00+02:00'

   result = some_func('2018-10-07T19:05:00+02:00')
   # result is now of type datetime.

In the above case, the type hint could be omitted for the same result: jsons
will recognize the timestamp from the string automatically. In case of a custom
type, you do need a type hint. The same goes for the return type; it could be
omitted in this case as well.

Similarly, you can decorate a function or method with ``@dumped`` as is done
below.

.. code:: python

   from datetime import datetime
   from jsons.decorators import dumped


   class SomeClass:
       @classmethod
       @dumped()
       def some_meth(cls, x):
           # x is now of type str, cls remains untouched.
           return datetime.now()

   result = SomeClass.some_meth(datetime.now())
   # result is now of type str.

In case of methods, like in the example above, the special ``self`` or ``cls``
parameters are not touched by the decorators ``@loaded()`` or ``@dumped()``.
Additionally, you can provide a type hint for any parameter (except ``self`` or
``cls``) or the return value. Doing so will make jsons attempt to dump into
that particular type, just like with
``jsons.dump(some_obj, cls=ParticularType)``.

Both ``@loaded`` and ``@dumped`` can be given the following arguments:

-  ``parameters`` (default ``True``): if positive, parameters will be taken into
   account.
-  ``returnvalue`` (default ``True``): if positive, the return value will be
   taken into account.
-  ``fork_inst`` (default ``JsonSerializable``): if given, this specific
   fork instance will be used for the loading/dumping operations.
-  ``**kwargs``: any other given keyword arguments are passed on to
   ``jsons.load`` or ``jsons.dump``.

The following arguments can be given only to ``@loaded``:

-  ``loader``: a ``jsons`` load function which must be one of ``jsons.load``,
   ``jsons.loads``, ``jsons.loadb``. The given function will be used to load
   from.

The following arguments can be given only to ``@dumped``:

-  ``dumper``: a ``jsons`` dump function which must be one of ``jsons.dump``,
   ``jsons.dumps``, ``jsons.dumpb``. The given function will be used to dump
   with.

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.

.. code:: python

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

A custom serializer must have the following form:

.. code:: python

   def someclass_serializer(obj: SomeClass, **kwargs) -> dict:
       # obj is the instance that needs to be serialized.
       # Make sure to return a type with a JSON equivalent, one of:
       # (str, int, float, bool, list, dict, None)
       return obj.__dict__

A custom deserializer must have the following form:

.. code:: python

   def someclass_serializer(obj: object, cls: type = None, **kwargs) -> object:
       # obj is the instance that needs to be deserialized.
       # cls is the type that is to be returned. In most cases, this is the
       # type of the object before it was serialized.
       return SomeClass(some_arg=obj['some_arg'])

Note that in both cases, if you choose to call any other (de)serializer within
your own, you should also pass the ``**kwargs`` upon calling.

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.

.. code:: python

   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:

.. code:: python

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

Customizing JsonSerializable
----------------------------
You can customize the behavior of the ``JsonSerializable`` class or extract a
new class from it. This can be useful if you are using ``jsons`` extensively
throughout your project, especially if you wish to have different
(de)serialization styles in different occasions.

.. code:: python

   forked = JsonSerializable.fork()
   forked.set_serializer(custom_serializer, datetime)  # A custom serializer.

   class Person(forked):
       def __init__(self, dt: datetime):
           self.dt = dt

   p = Person('John')
   p.json  # Will contain a serialized dt using 'custom_serializer'.

   jsons.dump(datetime.now())  # Still uses the default datetime serializer.

In the above example, a custom serializer is set to a fork of
``JsonSerializable``. The regular ``jsons.dump`` does not have this custom
serializer and will therefore behave as it used to.

You can also create a fork of a fork. All serializers and deserializers of the
type that was forked, are copied.

You can also define default ``kwargs`` which are then automatically passed as
arguments to the serializing and deserializing methods (``dump``, ``load``,
...). You can use ``with_dump`` and ``with_load`` to set default ``kwargs`` to
the serialization and deserialization process respectively.

.. code:: python

   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

You can, of course, also do this with a fork of ``JsonSerializable`` or you
can create a fork in the process by setting ``fork=True`` in ``with_dump`` or
``with_load``.


.. |PyPI version| image:: https://badge.fury.io/py/jsons.svg
   :target: https://badge.fury.io/py/jsons

.. |Docs| image:: https://readthedocs.org/projects/jsons/badge/?version=latest
   :target: https://jsons.readthedocs.io/en/latest/?badge=latest
   :alt: Documentation Status

.. |Build Status| image:: https://travis-ci.org/ramonhagenaars/geomodels.svg?branch=master
   :target: https://travis-ci.org/ramonhagenaars/jsons
.. |Scrutinizer Code Quality| image:: https://scrutinizer-ci.com/g/ramonhagenaars/jsons/badges/quality-score.png?b=master
   :target: https://scrutinizer-ci.com/g/ramonhagenaars/jsons/?branch=master
.. |Maintainability| image:: https://api.codeclimate.com/v1/badges/17d997068b3387c2f2c3/maintainability
   :target: https://codeclimate.com/github/ramonhagenaars/jsons/maintainability


