Preserve types when dumping and loading data with JSON
Project description
Classified JSON
Preserve types when dumping and loading data with json
- works on arbitrary typed data without pre-determined type-hints or data model
- supports dataclasses, dict with non-str keys, subclasses and more . . .
- Add custom hooks to support your own classes
- loads types recursively, including custom hooks
- Outputs to standard JSON
The serialized JSON content is solely for deserailizing back into Python. It is not meant to be human readable and is considered classified. It's off the record, on the qt, and very, very hush, hush.
pip install classifiedjson
from enum import Enum
from dataclasses import dataclass
from classifiedjson import dumps, loads
class Status(Enum):
IDLE = 0
RUNNING = 1
STOPPING = 2
@dataclass
class Job:
name: str
jobs = {}
jobs[Status.IDLE] = [Job("a"), Job("b")]
jobs[Status.RUNNING] = [Job("x"), Job("y"), Job("z")]
serialized = dumps(jobs)
# ... tempus fugit...
old_jobs = loads(serialized)
print([j.name for j in old_jobs[Status.RUNNING]])
output
['x', 'y', 'z']
Supported Types
- dataclasses
- dict (including non-string keys)
- list, tuple, set, frozenset
- str, int, bool, float, None
- enum, datetime
- array, bytes
- any subclasses of the above types
Installation
Python >=3.8
pip install classifiedjson
Examples
dataclass with arbitray typed data from the wild
from dataclasses import dataclass
from classifiedjson import dumps, loads
@dataclass
class Cat:
def speak(self) -> str:
return 'meow'
@dataclass
class House:
pet: Cat
@dataclass
class WildGator:
def speak(self):
return 'chomp'
house = House(WildGator())
print(house.pet.speak())
serialized = dumps(house)
# ... later gator...
old_house = loads(serialized)
print(old_house.pet.speak())
output
chomp
chomp
dict with non-string keys
from enum import Enum
from classifiedjson import dumps, loads
class DayPart(Enum):
MORNING = 0
NOON = 1
NIGHT = 2
forecast = {}
forecast[DayPart.MORNING] = "rainy"
forecast[DayPart.NOON] = "cloudy"
forecast[DayPart.NIGHT] = "sunny"
serialized = dumps(forecast)
# ... just over that ridge...
old_forecast = loads(serialized)
print(old_forecast[DayPart.NOON])
output
cloudy
list subclass
from enum import Enum
from classifiedjson import dumps, loads
class MyList(list):
def avg(self):
return sum(self)/len(self)
my_list = MyList([18,90,26,70,47,1])
print(my_list.avg())
serialized = dumps(my_list)
# ... some time later...
old_list = loads(serialized)
print(old_list.avg())
output
42.0
42.0
Custom Hooks
Add support to load and dump your own classes
from classifiedjson import dumps, loads, Factory
from enum import Enum
class Operator(Enum):
ADD = 1
MULTIPLY = 2
class Mathy:
def __init__(self, data: list[int], operator: Operator ) -> None:
self._data = data
self._operator = operator
self.scratch_notes: str = ""
def __str__(self):
return f"{self._data} scratch={self.scratch_notes}"
def operate(self, value: int):
if self._operator == Operator.ADD:
self._data = list(map(lambda x: x + value, self._data))
else:
self._data = list(map(lambda x: x * value, self._data))
def classifiedjson_serialize(self):
# save what we want
return { 'data': self._data, 'operator': self._operator}
@classmethod
def classifiedjson_deserialize(cls, factory: Factory, obj):
# call constructor with serailized data
return factory(data=obj['data'], operator=obj['operator'])
workspace = Mathy([1,3,5,7,11], Operator.ADD)
workspace.scratch_notes = "Hmm, I like dark chocolate"
workspace.operate(5)
print(workspace)
serialized = dumps(workspace)
# ... keep on truckin'....
old_workspace = loads(serialized)
old_workspace.operate(1)
print(old_workspace)
output
[6, 8, 10, 12, 16] scratch=Hmm, I like dark chocolate
[7, 9, 11, 13, 17] scratch=
Override how datetime is serailized with a custom hook function
from classifiedjson import dumps, loads, Factory, is_exact_match
from datetime import datetime, timezone
# the default datetime serialization preserves whatever timezone is there or not.
# So if there is no timezone, then it leaves it without one.
# here's how we can force a timezone to utc
def dt_serialize(obj):
if not is_exact_match(obj, datetime):
return NotImplemented
# e.g. datetime serialize to force timezone to utc
timestamp = obj.astimezone(timezone.utc).timestamp()
return timestamp
def dt_deserialize(factory: Factory, obj):
if not factory.is_exact_match(datetime):
return NotImplemented
return datetime.fromtimestamp(obj, timezone.utc)
def print_dt(dt):
tz = "<none>" if dt.tzinfo is None else dt.tzname()
print(f"{dt} zone={tz}")
dt = datetime(year=2024, month=2, day=2, hour=9, minute=1)
print_dt(dt)
serialized = dumps(dt, [dt_serialize])
# ...tempus fugit...
# yes, you can add as many custom hook functions as needed
dt_utc = loads(serialized, [dt_deserialize])
print_dt(dt_utc)
output (example)
2024-02-02 09:01:00 zone=<none>
2024-02-02 14:01:00+00:00 zone=UTC
Security
- No modules are loaded
- All data is type checked on deserialization
Copyright 2024 Shooting Soul Ventures, LLC
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
classifiedjson-1.0.0.tar.gz
(19.2 kB
view details)
Built Distribution
File details
Details for the file classifiedjson-1.0.0.tar.gz
.
File metadata
- Download URL: classifiedjson-1.0.0.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1fa28ab15f74d1cfc8c7d3a798ac35ca80cbe53eca5b9dd6f0adc26981b8b550 |
|
MD5 | 644de44729d24f2cb7cc51d4eaf3e0fd |
|
BLAKE2b-256 | 953349ca091f5ed71a96225c7fbfa9dd3907c351721e0546fac7d0816547b01d |
File details
Details for the file classifiedjson-1.0.0-py3-none-any.whl
.
File metadata
- Download URL: classifiedjson-1.0.0-py3-none-any.whl
- Upload date:
- Size: 22.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c778d1b805fe85b60ac4390ce07c84d0e48fe298bd49c6f5630f4eda1d22f426 |
|
MD5 | 2f1be3d8065236ce90836a7e5d83fa86 |
|
BLAKE2b-256 | 2ba378dae8034f2bdd444a90dbce7c0b967fb4267e8888f74339eb0ea4551ebc |