In C#, it is really easy to convert between class and JSON object, which brings tremendous convenience for Web development. Recently, I have to use a Python framework Django, and found that there’s no such feature as C#, so I implemented a shabby replica for that by myself.
What you will learn
In this article, you will get both a serializer and a de-serializer in Python to convert JSON string or
dictobject into or from a Python object. 😋
To start with, you must keep one concept in mind,
"Python object is merely a dictionary!"
Not that absolute, but generally speaking, yes.
We do not begin with scratch, and Python already make
json package part of its standard. So we just use it for fundamental parsing.
So… Let’s start serialization! 😆
For basic conversion between JSON string and Python
dict, we can simply use
json module to do the stuffs. However, if you have
datetime field in your Python object, you have to manually convert it to string, as JSON doesn’t have corresponding datetime value.
When you use
json.dump, you need a custom encoder to handle
datetimeis derived from
date, so you should place instance check of
date. And the format is custom, just make sure they corresponds each other in Encoder and Decoder.
If you are time zone aware, for example in Django project with
USE_TZ true, you may need to add an extra conversion. Just add a
astimezone method before
datetime.datedoesn’t seem to have such a method. Perhaps because it does not support timezone.
Then, correspondingly, when you use
json.loads, you need a custom decoder. However, what is different is that we need to override its
object_hook to tell it to try convert
datetime object. (It seems, in previous version, it is a method, instead of a member.)
After you spot a
str member, you should first check the format of it. Because
strptime(value, '%Y-%m-%d %H:%M:%S') will also treat
2023-06-20 as a valid one and return
2023-06-20 00:00:00, which will leave no chance for
And if you find out that the
str should be a
datetime.date, you still have to parse it with
datetime.datetime, because the other doesn’t support this method. Then, you need to convert it do
JSONEncoder, you can simply serialize a object into JSON string.
def serialize(obj) -> str:
Serialization related exceptions will be talked about at the end, since they are not the main topic.
If you don’t want to serialize a object into raw string, you can also serialize it into a
dict. Here I use
deserialize that will be elaborated later.
Compared to serialization, deserialization got one more problem - how to deserialize a JSON object into a object with desired class type? And this is the key point of this article.
Then, the deserialization can be implemented as such. If
cls is assigned, it will try to convert JSON string or object into the given class, and raise exception if type mismatch. Otherwise, it will simply return a
def deserialize(obj, cls=None):
There are two functions that play an important role here,
_construct_cls. They make sure the JSON object strictly match the given class, and try to build such a class from the JSON object. The implementation of them may be a little hard to understand, though. 😣
Well, although we can just construct class directly, and raise exception if anything wrong happens, we check the type first to give more detailed information if type mismatch occurs. The principle of this process is to convert target class into
dict first, and then compare all fields recursively. You just need to pay more attention to
def _check_type(src, cls):
And here is how exactly
list are handled here.
def __check_type_dict(src, model):
Now, you can check if a
dict is exactly a desired class or not.
After you check the type, you can go build a class from it. This is a little tricky, and was buggy. I refined it many times, and I guess it now should work in most cases. At least, no bug encountered ever after.
def _construct_cls(src, cls):
So… again, Python object is just a
dict with some extra fields. Emm… Hope it could make it easier for you to understand the code. 🥺
At last, I present to you the definition of custom exceptions that I used, to fit the last piece.
So, this is it. And… I guess that Python is not that diabolical. It can be convenient sometimes. Only, some times. 😶🌫️