Skip to main content
Join the official 2020 Python Developers SurveyStart the survey!

For serializing Python objects to JSON (dicts) and back

Project description

PyPI version Documentation Status Build Status Code Coverage Scrutinizer Code Quality Maintainability

   _
  (_)
   _ ___  ___  _ __  ___
  | / __|/ _ \| '_ \/ __|
  | \__ | (_) | | | \__ \
  | |___/\___/|_| |_|___/
 _/ | JSON SERIALIZATION
|__/      MADE EASY!

~ Any Python objects to/from JSON, easily! ~

  • Python 3.5+
  • Minimal effort to use!
  • No magic, just you, Python and jsons!
  • Human readible JSON without pollution!
  • Easily customizable and extendable!
  • Type hints for the win!

Example of a model to be serialized:

@dataclass
class Person:
    name: str
    birthday: datetime

Example of the serialization:

jsons.dump(Person('Guido van Rossum', birthday_guido))

Output after serialization:

{'birthday': '1956-01-31T12:00:00Z', 'name': 'Guido van Rossum'}

Read the documentation

Installation

pip install jsons

Usage

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.

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 Documentation

See the separate documentation page:

Documentation

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

Why not use the standard json library?

  • It’s quite a hassle to (de)serialize custom types: you need to write a subclass of json.JSONEncoder with specific serialization/deserialization code per custom class.
  • You will need to provide that subclass of json.JSONEncoder to json.dumps/json.loads every single time.

Examples

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

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.

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.

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.

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:

def someclass_serializer(obj: SomeClass, **kwargs) -> object:
    # 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:

def someclass_deserializer(obj: object, cls: type = None, **kwargs) -> SomeClass:
    # 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.

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

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.

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.

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.

Meta

Recent updates

0.8.1

  • Bugfix: Loading a verbose dump worked only shallow.

0.8.0

  • Feature: Dumping objects can be verbose.
  • Feature: Loading enums without use_enum_name can be with names or values.
  • Bugfix: Loading empty namedtuples caused a problem.

0.7.2

  • Bugfix: Package problem.

0.7.1

  • Bugfix: Deserializing named tuples raised an exception
  • Impl: Restructure of the code (splitted functions, moved to packages, …)

0.7.0

  • Doc: Improved API documentation
  • Feature: Support for loading Union or Optional
  • Feature: Extended strict-mode
  • Feature: Added custom Exceptions
  • Feature: Support for attr-getters
  • Bugfix: Local timezone for datetime serialization improved

0.6.1

  • Feature: Support for loading tuples of variable length

Contributors

Special thanks to the following contributors:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for jsons, version 0.8.1
Filename, size File type Python version Upload date Hashes
Filename, size jsons-0.8.1-py3-none-any.whl (36.4 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size jsons-0.8.1.tar.gz (29.6 kB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page