- Serialize JSON / Python Dictionary data into Python object based on a compact data
schema
.- Data
schema
is a python list[]
of many{}
. - Each
{}
in theschema
defines a property in yourJSON
data. - The easiest form of a property definition is
{'name':'my_property'}
which means:- Your
JSON
data MUST contain a property calledmy_property
. - Its value MUST be a non-empty value.
- Non-empty means that the value of
my_property
can not beNone
,""
,()
,[]
, or{}
.
- Your
- Additional options are available to give you more control over your data definition. Those options are:
nullable
,optional
,is_compound
,compound_serializer
,compound_schema
andtype
.- Option
nullable: True
means the value ofmy_property
can beNone
. - Option
optional: True
meansmy_property
may or may not exist in yourJSON
data.- In case
my_property
exist, verify all applicable options. - In case
my_property
doesn't exist, we ignoremy_property
.
- In case
- Option
is_compound: True
meansmy_property
is a nestedJSON
object or an Array ofJSON
objects.- When
is_compound: True
, you must provide eithercompound_serializer
orcompound_schema
so we can property serialize this nested data structure.compound_serializer
is aSerialJ
serializer class.compound_schema
has the same structure as the dataschema
.
- When
- Option
type
gives you the power to validate the value of each property in yourJSON
data. Currently supported type definitions are:'type': (bool,)
a boolean value.'type': (float,)
a floating point number.'type': (int,)
an integer.'type': (int, (1, 64, 343))
an enumeration of integers, this means that the value of aJSON
property should be in(1, 64, 343)
.'type': (int, range(1, 10, 3)
, a range of integers, this means that the value of aJSON
property should be inrange(1, 10, 3)
.'type': (int, lambda x: x % 2 == 0)
a user definedlambda
expression used to filter desired integer values, the above examplelambda
specifies the value of theJSON
property should be aeven
number.'type': (str,)
a string value.'type': (str, ('SUCCESS', 'FAILURE'))
an enumeration of strings, this means that the value of aJSON
property should be in('SUCCESS', 'FAILURE')
. Note that('SUCCESS', 'FAILURE')
is just an example here, you can define anything you like.'type': (str, 'email')
an email address.'type': (str, 'url')
a web url.'type': (str, 'ipv4')
an IPv4 address.'type': (str, 'ipv6')
an IPv6 address.'type': (str, 'uuid')
an UUID string.'type': (str, '[^@]+@[^@]+\.[^@]+')
a user definedregex
.
- Option
- Data
- Automatically validate every JSON properties defined in the
schema
based on varies additional options specified inschema
. - You are given convenient built-in methods that you can use to convert your data back to JSON encoded string or JSON / Python Dictionary.
- You have the flexibility of defining additional methods in your serializer class that utilize your data in anyway you want.
Name | Code |
---|---|
Basic Example | basic_ex.py |
Serialize Nested Json Data with compound_schema |
nested_ex2.py |
Serialize Nested Json Data with compound_serializer |
nested_ex1.py |
Data Type Validation: all in one example | typed_ex.py |
Data Type Validation: bool |
bool_data.py |
Data Type Validation: float |
float_data.py |
Data Type Validation: int |
int_data.py |
Data Type Validation: int enum |
int_enum_data.py |
Data Type Validation: int range |
int_ranged_data.py |
Data Type Validation: int lambda |
int_lambda_data.py |
Data Type Validation: str |
str_data.py |
Data Type Validation: str enum |
str_enum_data.py |
Data Type Validation: str email |
str_email_data.py |
Data Type Validation: str url |
str_url_data.py |
Data Type Validation: str uuid |
str_uuid_data.py |
Data Type Validation: str ipv4 |
str_ipv4_data.py |
Data Type Validation: str ipv6 |
str_ipv6_data.py |
Data Type Validation: str regex |
str_regex_data.py |
Let's first see a basic example.
from serial_j import SerialJ
class FruitBucket(SerialJ):
# define how our data should look like using `schema`.
schema = [
{'name': 'apple'},
{'name': 'orange'},
{'name': 'pineapple'},
]
# test data for FruitBucket
test1 = dict(
apple="good apple",
orange="very good orange",
pineapple="nice pineapple",
)
# serialize `test1` into `FruitBucket` object
fruits = FruitBucket(test1)
# `fruits` is a proper python object , which means that you can use
# `fruits.apple` syntax to retrieve the value of `apple`.
print(fruits.apple)
>>> good apple
# ...and other fruits too.
print(fruits.orange)
>>> very good orange
print(fruits.pineapple)
>>> nice pineapple
# you can get the JSON formatted string back too.
print(fruits)
>>> {"apple": "good apple", "orange": "very good orange", "pineapple": "nice pineapple"}
# interested to get the python dictionary back?
fruits_data = fruits.as_dict()
print(fruits_data)
>>> {'apple': 'good apple', 'orange': 'very good orange', 'pineapple': 'nice pineapple'}
Let's see how we can serialize more complex data structure into python object.
Define a nested data schema
called compound_schema
to serialize nested JSON
data.
from serial_j import SerialJ
class SnackBucket(SerialJ):
schema = [
{'name': 'apple'},
{'name': 'orange'},
{'name': 'pineapple'},
{'name': 'snack', 'is_compound': True,
'compound_schema': [
{'name': 'cheese', 'optional': True},
{'name': 'chocolate'},
{'name': 'chips', 'nullable': True},
],
},
]
test3 = dict(
apple="good apple",
orange="very good orange",
pineapple="nice pineapple",
snack=[
dict(
cheese="Feta",
chocolate="Ferrero Rocher",
chips=[]
),
dict(
chocolate="Swiss milk chocolate",
chips=["Cheetos", "Lays Classic Potato Chips", "Cool Ranch Doritos"]
),
]
)
mysnacks = SnackBucket(test3)
print(mysnacks)
>>> {"apple": "good apple", "orange": "very good orange", "pineapple": "nice pineapple",
>>> "snack": [{"cheese": "Feta", "chocolate": "Ferrero Rocher", "chips": []},
>>> {"chocolate": "Swiss milk chocolate", "chips":
>>> ["Cheetos", "Lays Classic Potato Chips", "Cool Ranch Doritos"]}]}
Define a separete data SerialJ
serializer called compound_serializer
to serialize nested JSON
data.
from serial_j import SerialJ
class Snack(SerialJ):
schema = [
# cheese is nice but is optional.
{'name': 'cheese', 'optional': True},
# chocolate is a MUST have.
{'name': 'chocolate'},
# chips is a must but we have to decide which kind later,
# so its value can be None, False, "", {}, [].
{'name': 'chips', 'nullable': True},
]
class NestedBucket(SerialJ):
schema = [
{'name': 'apple'},
{'name': 'orange'},
{'name': 'pineapple'},
{'name': 'snack', 'is_compound': True, 'compound_serializer': Snack}
]
# test data for NestedBucket
test2 = dict(
apple="good apple",
orange="very good orange",
pineapple="nice pineapple",
snack=dict(
chocolate="Ferrero Rocher",
chips=[] # yeah its a list of chips!
),
)
my_snacks = NestedBucket(test2)
print(my_snacks)
>>> {"apple": "good apple", "orange": "very good orange", "pineapple": "nice pineapple",
>>> "snack": {"chocolate": "Ferrero Rocher", "chips": []}}
a compact example that shows all data types currently suppoted by this package.
from serial_j import SerialJ
class TypedData(SerialJ):
schema = [
{'name': 'prop1', 'type': (int,)},
{'name': 'prop2', 'type': (int, (1, 64, 343))},
{'name': 'prop3', 'type': (int, range(1, 10, 3))},
{'name': 'prop4', 'type': (int, lambda x: x % 2 == 0)},
{'name': 'prop5', 'type': (str,)},
{'name': 'prop6', 'type': (str, ('SUCCESS', 'FAILURE'))},
{'name': 'prop7', 'type': (str, 'email')},
{'name': 'prop8', 'type': (str, 'url')},
{'name': 'prop9', 'type': (str, 'ipv4')},
{'name': 'prop10', 'type': (str, 'ipv6')},
{'name': 'prop11', 'type': (str, 'uuid')},
{'name': 'prop12', 'type': (str, '[^@]+@[^@]+\.[^@]+')},
{'name': 'prop13', 'type': (float,)},
{'name': 'prop14', 'type': (bool,)},
]
test1 = {
'prop1': 1,
'prop2': 64,
'prop3': 4,
'prop4': 2,
'prop5': "str",
'prop6': 'SUCCESS',
'prop7': '[email protected]',
'prop8': 'https://www.something.com/something-something/something/12345',
'prop9': '172.16.255.1',
'prop10': '2001:0db8:0a0b:12f0:0000:0000:0000:0001',
'prop11': 'c026dd66-86f2-498e-8c2c-858179c0c93d',
'prop12': '[email protected]',
'prop13': 0.1,
'prop14': True
}
data1 = TypedData(test1)
print(data1)
# >>> {"prop1": 1, "prop2": 64, "prop3": 4, "prop4": 2, "prop5": "str",
# >>> "prop6": "SUCCESS", "prop7": "[email protected]",
# >>> "prop8": "https://www.something.com/something-something/something/12345",
# >>> "prop9": "172.16.255.1", "prop10": "2001:0db8:0a0b:12f0:0000:0000:0000:0001",
# >>> "prop11": "c026dd66-86f2-498e-8c2c-858179c0c93d", "prop12": "[email protected]",
# >>> "prop13": 0.1, "prop14": true}