Package: typjson
typjsonType-safe JSON (de)serialization for Python. Compatible with mypy type hints.RequirementsPython 3.7 or newerFeaturesType safety in runtime and mypy compatibilitySupport for types out of the box:Primitive types:str,int,float,bool,Decimal,Nonedateas"%Y-%m-%d",datetimeas"%Y-%m-%dT%H:%M:%S%z",timeas"%H:%M:%S"UUIDasstrin format"8-4-4-4-12"chartype asstrof length 1Union[]and thereforeOptional[]Structure types:List[],Tuple[],Dict[str, T],Set[]Enum classesData classesSupport for custom encoders and decodersAPI similar to standardjson moduleSimple Usagefromtypimportjsonfromtypingimport*fromdatetimeimportdatefromdataclassesimportdataclass@dataclassclassAddress:street:strhouse:intapt:Optional[str]@dataclassclassPerson:first_name:strlast_name:strlanguages:List[str]address:Addressbirth_date:dateperson=Person("John","Smith",["English","Russian"],Address("Main",1,"2A"),date(year=1984,month=8,day=1))json_str=json.dumps(person,indent=2)loaded_person=json.loads(Person,json_str)assertperson==loaded_personValue ofjson_strthat is dumped and loaded in the code example above looks like:{"first_name":"John","last_name":"Smith","languages":["English","Russian"],"address":{"street":"Main","house":1,"apt":"2A"},"birth_date":"1984-08-01"}Type SafetyRuntimeWhat is type safety in Python? Since Python is dynamically typed language it's hard to provide any types guarantees before runtime. However types could be checked in run time. This is exactly what typjson library is doing.
Consider following example forAddresstype defined above:fromtypimportjsonfromtypingimport*fromdataclassesimportdataclass@dataclassclassAddress:street:strhouse:intapt:Optional[str]json_str="""{"street": "Main", "house": 1, "apt": 2}"""loaded_address=json.loads(Address,json_str)Theaptfield has type defined asOptional[str]however value provided in JSON is2which isnumbertype in JSON and it's obviously not compatible withOptional[str].
Respectivelyjson.loadscall will raiseJsonError:typ.encoding.JsonError: Value 2 can not be deserialized as typing.Union[str, NoneType]Calljson.loadswill either return instance of requested type with all nested types checked or raise a error. This is runtime type safety of typjson.Compile Time (mypy)Functions intyp.jsonmoduledumps,loads,dump,loadhave proper type hints. Therefore types could be validated withmypytool:json_str="""{"street": "Main", "house": 1, "apt": 2}"""loaded_address=json.loads(Address,json_str)loaded_address="some other address"This will produce error in mypy as type ofloaded_addressis inferred asAddress:error: Incompatible types in assignment (expression has type "str", variable has type "Address")This provides type safety in compile time.API Overviewtypjson API is similar tojsonmodule API. Main functions are defined intyp.jsonmodule. Most useful functions aretyp.json.loadsandtyp.json.dumps. If something went wrong during JSON encoding/decoding thenJsonErroris raised.
In facttyp.jsonfunctions are usingjsonmodule under the hood for final conversion between python structures and JSON.List of supported types if providedhere.Custom Encodingsection describes how any type could be supported in addition to types that are supported out of the box.Supported TypesPrimitive TypesPython typeJSON typeNotesintnumberfloatnumberdecimal.Decimalnumberbooleanbooleantyp.typing.charstringstring with length 1strstringuuid.UUIDstringlower case hex symbols with hyphens as 8-4-4-4-12datetime.datestringISO 8601 yyyy-mm-dddatetime.datetimestringISO 8601 yyyy-mm-ddThh:mm:ss.ffffffdatetime.timestringISO 8601 hh:mm:ss.fffffftyp.typing.NoneTypetype(None)nullNon Primitive TypesPython typeJSON typeNotesList[T]arrayhomogeneous, items encodedDict[str, T]objectfields values of T encodedSet[T]arrayhomogeneous, items of T encodedTuple[T, K, ...]arrayheterogeneous, items of T, K, ... encodedUnion[T, K, ...]look for T, K, ...T, K, ... encodedlistarrayheterogeneous, items are encodeddictobjecttuplearrayheterogeneous, items are encodedEnum classeslook for member typeenum members are encoded according to their typesclass decorated with@dataclassobjectfield types are respectedclass decorated with@unionobjectobject with single fieldAnyany typeanythingNull-safetyAll types can not haveNonevalue besidesNoneTypeakatype(None).Optional[T]allowsNonevalue.
So if nullablestris neededOptional[str]would be a good fit.Optional[T]type is in factUnion[T, NoneType]therefore in typjson it's supported viaUnion[]support.
Because of thisOptional[T]is not listed above since it's just aUnion.Custom EncodingIn fact all types that are supported out of the box are supported via encoders and decoders. Examples of custom encoder and decoder are provided just for basic understanding. For deeper insight the one might be interested to look at source code oftyp.encodingmodule.Custom Encodertyp.json.dumpandtyp.json.dumpsfunctions take list of encoders as a parameter.
Those encoders are custom encoders that are used in addition to standard built-in encoders.
Let's implement custom encoder that will code all integers as strings in JSON:fromtyp.encodingimportUnsupported,check_typedefencode_int_custom(encoder,typ,value):iftyp!=int:# if this encoder is not applicable to the typ it should return UnsupportedreturnUnsupported# there's a helper function checking that value is instance of specified type - intcheck_type(int,value)# return encoded valuereturnstr(value)fromtypimportjsonassertjson.dumps([3,4,5],encoders=[encode_int_custom])=='["3", "4", "5"]'In the code aboveencode_int_customis provided intotyp.json.dumpscall and it's used prior standard built-inintencoding. As it's deemostrated in the assert it successfully encoded integers as strings. Please never do this in real life - this code is provided only for demonstration purposes.Encoder function is defined as:Callable[['Encoder', Type[K], K], Union[Any, UnsupportedType]]There's anencoderparameter of every custom encoder which holds instance ofEncoder. It is useful for encoding nested types, like lists or classes, etc.Custom Decodertyp.json.loadandtyp.json.loadsfunctions take list of decoders as a parameter.
Similarly toencodingit's useful for custom decoding logic.
Here's a mirror example for decodinginttype from strings in JSON:fromtyp.encodingimportUnsupported,check_typedefdecode_int_custom(decoder,typ,json_value):iftyp!=int:# if this encoder is not applicable to the typ it should return UnsupportedreturnUnsupported# check that JSON has string in the json_valuecheck_type(str,json_value)# return decoded valuereturnint(json_value)fromtypimportjsonassertloads(List[int],'["3", "4", "5"]',decoders=[decode_int_custom])==[3,4,5]Decoder function is defined as:Callable[['Decoder', Type[K], Any], Union[K, UnsupportedType]].API Referencetyp.json.dumpstyp.json.dumps(value: T, typ: Optional[Type[T]] = None, case: CaseConverter = None, encoders: List[EncodeFunc] = [], indent: Optional[int] = None) -> strSerialize value to a JSON formatted str using specified type.valuePython object to be serialized to JSON.typtype information forvalue.
IfNoneis provided then actual type ofvalueis used otherwisevalueis checked to be valid instance oftyp.casecase converter, seefields case.encoderslist of custom encoders, seecustom encoding.indentoptional non-negative indent level for JSON. IfNoneis provided then JSON is represented as single line without indentation.Returns JSON string or raisesJsonError.typ.json.dumptyp.json.dump(fp: IO[str], value: T, typ: Optional[Type[T]] = None, case: CaseConverter = None, encoders: List[EncodeFunc] = [], indent: Optional[int] = None) -> NoneSerialize value as a JSON formatted stream.fpstream to write JSON to.Other arguments have the same meaning as intyp.json.dumps.typ.json.loadstyp.json.loads(typ: Type[T], json_str: str, case: CaseConverter = None, decoders: List[DecodeFunc] = []) -> TDeserialize json_str to a Python object of specified type.typtype to deserialize JSON into.json_strstring containing JSON.casecase converter, seefields case.decoderslist of custom decoders, seecustom encoding.Returns instance ofMor raisesJsonError.typ.json.loadtyp.json.load(fp: IO[str], typ: Type[T], case: CaseConverter = None, decoders: List[DecodeFunc] = []) -> TDeserialize stream to a Python object of specified type.fpstream to read JSON from.Other arguments have the same meaning as intyp.json.loadstyp.json.JsonError (defined as typ.encoding.JsonError)JsonErrorraised in case of any issue during encoding/decoding JSON data according to type information provided.