Home > Net >  JSON serializer class - why json.dumps() works while json.dump() does not?
JSON serializer class - why json.dumps() works while json.dump() does not?

Time:07-11

I want to json.dump() a dictionary that has keys of datetime.date type, and to do so I made a serializer class to encode the date keys as strings. To my surprise it works with json.dumps(), but throws an error with json.dump(). At the end I was able to dump using a workaround, but I'd like to understand what happens here: why does the serializer class work with json.dumps() and does not work with json.dump()?

I tried writing a similar function to use with default= instead of cls=, tried rewriting this back and forth, tried to adapt some examples I found in the web, but I always ended up with the same error. I don't know what I'm missing here, even after reading the docs. Please enlighten me what do I need to change here to be able to serialize the dates and dump the dictionary.

Here is the MRE (Python 3.8.6):

import json
import datetime


class JSONSerializer(json.JSONEncoder):
    @staticmethod
    def convert_if_date(_date):
        if isinstance(_date, datetime.date):
            return _date.strftime('%Y-%m-%d')
        return _date

    def date_insensitive_encode(self, obj):
        if isinstance(obj, dict):
            return {self.convert_if_date(k): v for k, v in obj.items()}
        return obj

    def encode(self, obj):
        return super(JSONSerializer, self).encode(
            self.date_insensitive_encode(obj))


test_dict = {datetime.date(2022, 7, 10): 'OK'}

print(json.dumps(test_dict, cls=JSONSerializer))
# this prints: {"2022-07-11": "OK"}

with open('dump.json', 'w') as w:
    json.dump(test_dict, w, cls=JSONSerializer)
# but this throws a TypeError

Full error:

Traceback (most recent call last):
  File "E:/Progz/Python/AppKH/datacontrol/so_mre.py", line 28, in <module>
    json.dump(test_dict, w, cls=JSONSerializer)
  File "C:\Program Files\Python38\lib\json\__init__.py", line 179, in dump
    for chunk in iterable:
  File "C:\Program Files\Python38\lib\json\encoder.py", line 431, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "C:\Program Files\Python38\lib\json\encoder.py", line 376, in _iterencode_dict
    raise TypeError(f'keys must be str, int, float, bool or None, '
TypeError: keys must be str, int, float, bool or None, not date

CodePudding user response:

From previous experience, I thought the encode() method of the base json.JSONEncoder class was only used the when the dumps() method was called, and not when dump() was, however that's apparently not the case (so my previous solution didn't apply)…but the good news is I was able to do what I think you want done only in a different, simpler way that works with both methods — and it only requires overriding the encode() method to do so.

from collections.abc import MutableMapping
import json
import datetime


class JSONSerializer(json.JSONEncoder):
    def encode(self, obj):
        # Convert dictionary keys that are datatime.dates into strings.
        if isinstance(obj, MutableMapping):
            for key in list(obj.keys()):
                if isinstance(key, datetime.date):
                    strkey = key.strftime('%Y-%m-%d')
                    obj[strkey] = obj.pop(key)

        return super().encode(obj)


test_dict = {datetime.date(2022, 7, 10): 'OK'}

print(json.dumps(test_dict, cls=JSONSerializer))  # -> {"2022-07-11": "OK"}

with open('dump.json', 'w') as w:
    json.dump(test_dict, w, cls=JSONSerializer)  # # Works now, too.
  • Related