Package: uniserde
Convention based, effortless serialization and deserializationuniserdecan convert Python classes to/from JSON and BSON without any input
from your side. Simply define the classes, and the library does the rest.Define your types as classes with type annotations, and call one ofuniserde's
serialization/deserialization functions:fromuniserdeimportSerdefromdatetimeimportdatetime,timezonefromdataclassesimportdataclassfrombsonimportObjectId@dataclassclassPerson(Serde):id:ObjectIdname:strbirth_date:datetimebetty=Person(id=ObjectId(),name="Betty",birth_date=datetime(year=1988,month=12,day=1,tzinfo=timezone.utc),)print(betty.as_json())This will print a dictionary similar to this one{'id':'62bc6c77792fc617c52499d0','name':'Betty','birthDate':'1988-12-01T00:00:00+00:00'}You can easily convert this to a string using Python's built-injsonmodule if
that's what you need.APIThe API is extremely simple. Functions/Classes you might be interested in are:as_json,as_bsonGiven a class with type annotations, these create a JSON/BSON like dictionary.
You can feed those into functions likejson.dump, or use them as is.from_json,from_bsonGiven a JSON/BSON like dictionary and Python type, these will instantiate the
corresponding Python class. RaiseSerdeErrorif the values are invalid.Serdeis a helper class you can optionally apply to your models. It adds the
convenience functionsas_json,as_bson,from_json, andfrom_bsondirectly to the models.Sometimes a class simply acts as a type-safe base, but you really just want to
serialize the children of that class. In that case you can decorate the class
with@as_child. This will store an additionaltypefield in the result, so
the correct child class can be instantiated when deserializing.as_mongodb_schemaautomatically creates JSON schemas compatible with MongoDB
from modelsCustom serialization / deserialization can be achieved by inheriting from theSerdeclass and overriding theas_json,as_bson,from_json,from_bsonand/oras_mongodb_schemamethods.The library also exposes a couple handy type definitions:Jsonable,Bsonable-- Any type which can occur in a JSON / BSON file
respectively, i.e. (bool, int, float, ...)JsonDoc,BsonDoc-- A dictionary mapping strings toJsonables /BsonableLazy DeserializationNormally, serialization happens all at once: You telluniserdeto create a
class instance from a JSON,uniserdeprocesses all of the fields and returns
the finished class.This works great, but can be wasteful if you are working with large documents
and only need to access few fields. To help with this, you can passlazy=Truewhen deserializing any object.uniserdewill then hold off deserializing
fields until they are accessed for the first time, saving precious processing
time.A word of caution:Data is validated as it is deserialized. Since lazy
deserialization defers work until the data is accessed, this means any data you
don't access also won't be validated. Thus, lazy serialization can be a very
powerful tool for speeding up interactions with large objects, but you should
only use when you are absolutely certain the data is correct. (For example
because you have just fetched the object from your own, trusted, database.)Types & ConventionsThe library tries to stick to the naming conventions used by the target formats:names in JSON are written inlowerCamelCase, as is convention in JavaScriptBSON uses the same conventions as JSONPython class names must be inUpperCamelCasePython fields must be inall_lower_casePython enum values must be inALL_UPPER_CASEJSONPythonJSONNotesboolboolintfloatfloatfloatstrstrTuplelistListlistSetlistOptionalvalue orNoneAnyas-isLiteral[str]strenum.EnumstrEnum values are mapped to theirname(NOT value!)enum.FlagList[str]Each flag is encoded the same way a regularenum.Enumvalue would.custom classdictEach attribute is stored as key, inlowerCamelCase. If marked withas_child, an additionaltypefield is added.bytesstrbase64 encodeddatttime.datetimestras ISO 8601 - with timezone. Naïve datetimes are intentionally not supported. Do yourself a favor and don't use them.datetime.timedeltafloatduration, in secondsDict[str, ...]dictbson.ObjectIdstrBSONBSON uses the same conventions as JSON, with just a few changesPythonBSONNotescustom classdictSame as JSON, but any fields namedidare renamed to_idto match MongoDB.bytesbytesdatetime.datetimedatetime.datetimeSerialization requires a timezone be set. Deserialization imputes UTC, to match MongoDB.bson.ObjectIdbson.ObjectIdMongoDB Schema GenerationIf you are working with MongoDB you will come to appreciate the automatic schema
generation. Callinguniserde.as_mongodb_schemaon any supported class will
return a MongoDB compatible JSON schema without hassle.For example, here's the result ofuniserde.as_mongodb_schema(Person)with thePersonclass above:{'type':'object','properties':{'_id':{'bsonType':'objectId'},'name':{'type':'string'},'birthDate':{'bsonType':'date'}},'additionalProperties':False,'required':['_id','name','birthDate']}TODOSupport forUnionis currently very limited. Really onlyOptionalis
supported (which Python internally maps toUnion)Literalcurrently only supports stringsExtendas_child, to allow marking some classes as abstract. i.e. their
parents/children can be serialized, but not those classes themselvesBeing able to specify additional limitations to fields would be nice:must match regexminimum / maximumcustom validation functionsmore Unit tests (custom de-serializers!?)Add more examples to the READMEshow custom serializers/deserializersrecommended usageregression trackingcallinguniserde.serializeon nonclasses causes problems, because the
serializationas_typeis guessed incorrectly. e.g.[1, 2, 3]will be
incorrectly serialized aslistrather thanList[int].