5
\$\begingroup\$

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)
Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked May 13, 2020 at 0:01
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

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.

answered Mar 19 at 0:35
\$\endgroup\$
1
  • \$\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\$ Commented Mar 19 at 14:36

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.