Define your JSON schema as Python dataclasses

Overview

codecov Build Status

nvelope

Define your JSON schema as Python dataclasses

Installation

pip install nvelope

The problem it solves

This is basically sommething like JSON-schema, but it works with static type checking, since the classes you define are just regular python dataclasses which can (and should) be type checked with mypy library.

It also lets not to just define the structure of your JSON data in a single place in your python code, but also to define custom checks and conversions from/to JSON for any type you want.

Original use case

Say you have two microservices communicating via JSON messages, both written in python.

You may define a shared package with the messages definition and use the model's .as_json() method on one end to serialize the message and .from_json() on the other to convert it into a DTO, checking and modifying the fields and their values along the way exactly how you defined it in a single place.

Combining this with static type checking (and maybe some unit tests) you can ensure that any message one microservice can send, the other can read as long as they use the same model to pack/unpack their JSONs.

Usage

Say you have a JSON representing a user in your app looking something like this

{
    "id": 530716139,
    "username": "johndoe",
    "language_code": "en"
}

You define an envelope for it

from dataclasses import dataclass
from typing import Optional

from nvelope import (Obj, int_conv, string_conv)

@dataclass      # note the @dataclass decorator, it is important
class User(Obj):
    _conversion = {
        "id": int_conv,
        "language_code": string_conv,
        "username": string_conv,
    }

    id: int
    language_code: str
    username: str

Now you have a model that knows how to read data from the JSON (not the raw string, actually, but to the types that are allowed by the standard json.dumps function e.g. dict, list, str, int, float, bool, None ) ...

user = User.from_json(
    {
        "id": 530716139,
        "username": "johndoe",
        "language_code": "en"
    }
)

... and knows how to convert itself into JSON

User(
    id=530716139,
    username="johndoe",
    language_code="en",
).as_json() 

# returns a dictionary {
#     "id": 530716139,
#     "username": "johndoe",
#     "language_code": "en"
# }

Compound envelopes

You can also define compound envelopes.

Say we want to define a message and include info about the sender. Having defined the User envelope, we can do it like this:

from nvelope import CompoundConv

@dataclass
class Message(Obj):
    _conversion = {
        "message_id": int_conv,
        "from_": CompoundConv(User),
        "text": string_conv,
    }

    from_: User
    text: str
    message_id: int

and use it the same way:

# reading an obj from json like this

Message.from_json(
    {
        "message_id": 44,
        "text": "hello there",
        "from_": {
            "id": 530716139,
            "username": "johndoe",
            "language_code": "en"
        }
    }
)

# and dumping an object to json like this
Message(
    message_id=44,
    text="whatever",
    from_=User(
        id=530716139,
        username="johndoe",
        language_code="en",
    )
).as_json()

Arrays

This is how you define arrays:

from nvelope import Arr


class Users(Arr):
    conversion = CompoundConv(User)


# Same API inherited from nvelope.Compound interface

Users.from_json(
    [
        {
            "id": 530716139,
            "username": "johndoe",
            "language_code": "en",
        },
        {
            "id": 452341341,
            "username": "ivandrago",
            "language_code": "ru",
        }
    ]
)

Users(
    [
        User(
            id=530716139,
            username="johndoe",
            language_code="en",
        ),
        User(
            id=452341341,
            username="ivandrago",
            language_code="ru",
        ),
    ]
).as_json()

Field aliases

At some point you may need to define an envelope for an API containing certain field names which cannot be used in python since they are reserved keywords.

There's a solution for this:

from nvelope import ObjWithAliases

@dataclass
class Comment(ObjWithAliases):
    _conversion = {
        "text": string_conv,
        "from_": int_conv,
    }
    
    
    _alias_to_actual = {
        "from_": "from",
    }
    
    text: str
    from_: User

In this case from key gets replaced by from_ in the python model.

Missing and optional fields

There's a difference between fields that can be set to None and fields which may be missing in the JSON at all.

This is how you specify that a some field may be missing from the JSON and that's OK:

from typing import Optional

from nvelope import MaybeMissing
from nvelope import OptionalConv

@dataclass
class Comment(ObjWithAliases):
    _alias_to_actual = {
        "from_": "from",
    }
    
    text: str
    img: Optional[str]          # this field can be set to None (null), but is must always be present in the JSON
    from_: MaybeMissing[User]   # this field can be missing from JSON body

    _conversion = {
        "text": string_conv,
        "img": OptionalConv(string_conv),   # note the wrapping with OptionalConv
        "from_": int_conv,
    }

This is how you check if the MaybeMissing field is actually missing

comment.from_.has()     # returns False if the field is missing

and this is how you get the value:

comment.value()     # raises an error if there's no value, 
                    # so it is recommended to check the output of .has()
                    #  before calling .value() 

Custom conversions

You may define a custom conversions inheriting from nvelope.nvelope.Conversion abstract base class or using nvelope.nvelope.ConversionOf class.

For example, this is how datetime_iso_format_conv is defined:

from nvelope import WithTypeCheck, ConversionOf

datetime_iso_format_conv = WithTypeCheck(
    datetime.datetime,
    ConversionOf(
        to_json=lambda v: v.isoformat(),
        from_json=lambda s: datetime.datetime.fromisoformat(s),
    ),
)

Say we want to jsonify a datetime field as POSIX timestamp, instead of storing it in ISO string format.

datetime_timestamp_conv = ConversionOf(
    to_json=lambda v: v.timestamp(),
    from_json=lambda s: datetime.datetime.fromtimestamp(s),
)

We could also add WithTypeCheck wrapper in order to add explicit check that the value passed into .from_json() is indeed float.

datetime_timestamp_conv = WithTypeCheck(
    float,
    ConversionOf(
        to_json=lambda v: v.timestamp(),
        from_json=lambda s: datetime.datetime.fromtimestamp(s),
    )
)
You might also like...
simplejson is a simple, fast, extensible JSON encoder/decoder for Python

simplejson simplejson is a simple, fast, complete, correct and extensible JSON http://json.org encoder and decoder for Python 3.3+ with legacy suppo

jq for Python programmers Process JSON and HTML on the command-line with familiar syntax.

jq for Python programmers Process JSON and HTML on the command-line with familiar syntax.

A JSON utility library for Python featuring Django-style queries and mutations.

JSON Enhanced JSON Enhanced implements fast and pythonic queries and mutations for JSON objects. Installation You can install json-enhanced with pip:

Python script for converting .json to .md files using Mako templates.

Install Just install poetry and update script dependencies Usage Put your settings in settings.py and .json data (optionally, with attachments) in dat

json|dict to python object

Pyonize convert json|dict to python object Setup pip install pyonize Examples from pyonize import pyonize

Editor for json/standard python data
Editor for json/standard python data

Editor for json/standard python data

Python script to extract news from RSS feeds and save it as json.

Python script to extract news from RSS feeds and save it as json.

A Python tool that parses JSON documents using JsonPath

A Python tool that parses JSON documents using JsonPath

Simple Python Library to convert JSON to XML
Simple Python Library to convert JSON to XML

json2xml Simple Python Library to convert JSON to XML

Comments
  • Recursive definition

    Recursive definition

    Is there a way for recursive definitions? So for example let's say we want to consider sections in an article. A section have a title and maybe some subsections. The corresponding json could look like:

    {
        "title": "This is a really nice title",
        "sections": [
            {
                "title": "Oh this title is even nicer"
                "sections: [
                    {
                         "title: "Not so nice title, no subsections"
                    }
                ]
            },
            {
                 "title": "Section without subsection"
            }
        ]
    

    So we could start with:

    @dataclass
    class Section(Obj):
        _conversion = {"title": string_conv}
    
        title: str
    

    But obviously the maybe subsections are missing. Is there a way to model that? Thanks.

    opened by donpatrice 4
  • Descriptors

    Descriptors

    From readme I see

    @dataclass    
    class User(Obj):
        _conversion = {
            "id": int_conv,
            "language_code": string_conv,
            "username": string_conv,
        }
    
        id: int
        language_code: str
        username: str
    

    Which makes me curious on why to define an argument named _conversion, is this a design decision for some reason?

    What about implementing that using the descriptor protocol?

    @dataclass    
    class User(Obj):
        id: int = IntField()
        language_code: str = StringField()
        username: str = StringField()
    

    And having all *Field to be implementation of descriptors such as:

    class IntField:
    
        def __set_name__(self, owner, name):
            self.public_name = name
            self.private_name = '_' + name
    
        def __get__(self, obj, objtype=None):
            value = getattr(obj, self.private_name)
            logging.info('Accessing %r giving %r', self.public_name, value)
            return int(value)  # conversion here on reading
    
        def __set__(self, obj, value):
            logging.info('Updating %r to %r', self.public_name, value)
            setattr(obj, self.private_name, int(value))  # conversion here on writing
    
    
    opened by rochacbruno 1
Releases(v1.1.0)
  • v1.1.0(May 30, 2022)

  • v1.0.0(Feb 3, 2022)

    • JSON schema generation in compound objects via .schema() method;
    • Conversion interface now requires .schema() method returning a JSON schema;
    • moved functionality of ObjWithAliases to Obj;
    • added possibility of storing undefined JSON fields in a model instance;
    • validated class decorator checking the correctness of a Obj and Arr subclass;
    • datetime_timestamp_conv to store a datetime as POSIX timestamp float.
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Dec 10, 2021)

  • v0.3.1(Nov 23, 2021)

Owner
A big fan of high quality software engineering
Json GUI for No Man's Sky save file

NMS-Save-Parser Json GUI for No Man's Sky save file GUI python NMS_SAVE_PARSER.py [optional|save.hg] converter only python convert.py usage: conver

2 Oct 19, 2022
A Cobalt Strike Scanner that retrieves detected Team Server beacons into a JSON object

melting-cobalt 👀 A tool to hunt/mine for Cobalt Strike beacons and "reduce" their beacon configuration for later indexing. Hunts can either be expans

Splunk GitHub 150 Nov 23, 2022
Convert your subscriptions csv file into a valid json for Newpipe!

Newpipe-CSV-Fixer Convert your Google subscriptions CSV file into a valid JSON for Newpipe! Thanks to nikcorg for sharing how to convert the CSV into

Juanjo 44 Dec 29, 2022
MOSP is a platform for creating, editing and sharing validated JSON objects of any type.

MONARC Objects Sharing Platform Presentation MOSP is a platform for creating, editing and sharing validated JSON objects of any type. You can use any

CASES Luxembourg 72 Dec 14, 2022
A fast streaming JSON parser for Python that generates SAX-like events using yajl

json-streamer jsonstreamer provides a SAX-like push parser via the JSONStreamer class and a 'object' parser via the ObjectStreamer class which emits t

Kashif Razzaqui 196 Dec 15, 2022
Atom, RSS and JSON feed parser for Python 3

Atoma Atom, RSS and JSON feed parser for Python 3. Quickstart Install Atoma with pip: pip install atoma

Nicolas Le Manchet 95 Nov 28, 2022
Random JSON Key:Pair Json Generator

Random JSON Key:Value Pair Generator This simple script take an engish dictionary of words and and makes random key value pairs. The dictionary has ap

Chris Edwards 1 Oct 14, 2021
Package to Encode/Decode some common file formats to json

ZnJSON Package to Encode/Decode some common file formats to json Available via pip install znjson In comparison to pickle this allows having readable

ZINC 2 Feb 02, 2022
Same as json.dumps or json.loads, feapson support feapson.dumps and feapson.loads

Same as json.dumps or json.loads, feapson support feapson.dumps and feapson.loads

boris 5 Dec 01, 2021
Make JSON serialization easier

Make JSON serialization easier

4 Jun 30, 2022
Define your JSON schema as Python dataclasses

Define your JSON schema as Python dataclasses

62 Sep 20, 2022
Marshall python objects to and from JSON

Pymarshaler - Marshal and Unmarshal Python Objects Disclaimer This tool is in no way production ready About Pymarshaler allows you to marshal and unma

Hernan Romer 9 Dec 20, 2022
Convert Wii UI formats to JSON5 and vice versa

Convert Wii UI formats to JSON5 and vice versa

Pablo Stebler 11 Aug 28, 2022
Easy JSON wrapper modfied to wrok with suggestions

🈷️ Suggester Easy JSON wrapper modfied to wrok with suggestions. This was made for small discord bots, for big bots you should not use this. 📥 Usage

RGBCube 1 Jan 22, 2022
JSONManipulator is a Python package to retrieve, add, delete, change and store objects in JSON files.

JSONManipulator JSONManipulator is a Python package to retrieve, add, delete, change and store objects in JSON files. Installation Use the package man

Andrew Polukhin 1 Jan 07, 2022
Python script to extract news from RSS feeds and save it as json.

Python script to extract news from RSS feeds and save it as json.

Alex Trbznk 14 Dec 22, 2022
An tiny CLI to load data from a JSON File during development.

JSON Server - An tiny CLI to load data from a JSON File during development.

Yuvraj.M 4 Mar 22, 2022
Simple, minimal conversion of Bus Open Data Service SIRI-VM data to JSON

Simple, minimal conversion of Bus Open Data Service SIRI-VM data to JSON

Andy Middleton 0 Jan 22, 2022
Fileson - JSON File database tools

Fileson is a set of Python scripts to create JSON file databases

Joonas Pihlajamaa 2 Feb 02, 2022
JsonParser - Parsing the Json file by provide the node name

Json Parser This project is based on Parsing the json and dumping it to CSV via

Ananta R. Pant 3 Aug 08, 2022