Below are two classes for encoding and decoding JSON. Are there any improvements which can be made?
import datetime
import json
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
"""
This method will only be called if an object is passed to the parent JSON encoder in the case
it doesn't know how to deal with.
In case the object passed to the encode method of JSON that is by default json serializable [eg.: list]
this method will not be called
"""
if isinstance(obj, datetime.datetime):
# Return a JSON object where each parameter of the datetime object is a key in a dictionary.
return {
'_type': 'datetime',
'year': obj.year,
'month': obj.month,
'day': obj.day,
'hour': obj.hour,
'minute': obj.minute,
'second': obj.second
}
elif isinstance(obj, datetime.timedelta):
return {
'_type': 'timedelta',
'days': obj.days,
'seconds': obj.seconds,
'microseconds': obj.microseconds
}
# In case the object passed is not serializable by default and,
# is not implemented above; the default method will fall through here. In which case:
# Pass the object back to the default JSON handler and:
return super().default(obj) # Let the default JSON handler output the error: not serializable.
class CustomDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
@staticmethod
def object_hook(obj):
decodes = {'datetime': datetime.datetime, 'timedelta': datetime.timedelta}
if '_type' in obj:
data_type = obj.pop('_type')
# This line will return either:
# a decoded object of a type specified in the "decodes" dictionary
# or obj without any modification to it's type in case the type is not implemented yet.
return decodes.get(data_type, lambda **kwargs: kwargs)(**obj)
Testing:
now = datetime.datetime.now()
encoded_datetime_data = json.dumps(now, cls=CustomEncoder, indent=2)
decoded_datetime_data = json.loads(encoded_datetime_data, cls=CustomDecoder)
encoded_timedelta_data = json.dumps(datetime.timedelta(0), cls=CustomEncoder, indent=2)
decoded_timedelta_data = json.loads(encoded_timedelta_data, cls=CustomDecoder)
encoded_array = json.dumps([1, 2, 3, 4], cls=CustomEncoder, indent=2)
decoded_array = json.loads(encoded_array, cls=CustomDecoder)
print(encoded_datetime_data)
print(decoded_datetime_data)
print(type(decoded_datetime_data))
print(encoded_timedelta_data)
print(decoded_timedelta_data)
print(type(decoded_timedelta_data))
print(encoded_array)
print(decoded_array)
print(type(decoded_array))
class Nothing:
pass
# This will fail: (TypeError: Object of type Nothing is not JSON serializable)
encoded_nothing_data = json.dumps(Nothing(), cls=CustomEncoder)
decoded_nothing_data = json.loads(encoded_nothing_data, cls=CustomDecoder)
1 Answer 1
Are there any improvements which can be made?
[to the target code]
No.
Are there any improvements which can be made to the test code?
Oh, yes!
As written in the OP source code, we have a bunch
of print
's for a maintenance engineer to
eyeball and manually evaluate, and an implicit
promise
that we won't do a fatal raise
.
It would be much, much better for an
automated
unit test to self.assertEqual()
that each computed result matches some constant.
now = datetime.datetime.now()
@TobySpeight observes that we're usually much
better off with a well chosen constant than
a call to .now()
in a good automated test suite.
This is for the same reason we take care to seed PRNGs
at start of suite -- we want reproducibility.
Of course this would have immediately surfaced
the moment you started asserting encoded_array
equals a given constant.
-
\$\begingroup\$ Might be worth noting that
datetime.now()
is a poor choice for testing, since it's not reproducible. It's particularly bad at times like 2012年12月12日T12:12:12, where we'd be unable to detect if values were swapped. \$\endgroup\$Toby Speight– Toby Speight2025年03月19日 14:36:45 +00:00Commented Mar 19 at 14:36