Home > OS >  Can not encode a dict to JSON within a custom encoder
Can not encode a dict to JSON within a custom encoder

Time:11-13

I am trying to make a custom JSON encoder and I ran into this problem I don't understand.

I basically followed the instructions of the official documentation

Encoding a dictionary is clearly possible with the standard JSON encoder. But if I convert the custom object to a dict and pass it to the default encoder, I get an: TypeError: Object of type dict is not JSON serializable.

Clearly there is something I am missing here. Can someone explain to me the behavior of the toy example below?

import json

class Klass():
    def __init__(self,number):
        self.number = number

    def __dict__(self):
        return {"number": self.number}

class CustomEncoder(json.JSONEncoder):
    def default(self,obj):
        if isinstance(obj,Klass):
            obj=obj.__dict__()
        return json.JSONEncoder.default(self, obj)

json.dumps({"number" : 10})                         # works
json.dumps({"number" : 10},cls=json.JSONEncoder)    # works
json.dumps({"number" : 10},cls=CustomEncoder)       # works
json.dumps(Klass(10).__dict__(), cls=CustomEncoder) # works
json.dumps({"Test":Klass(10).__dict__()}, cls=CustomEncoder) #works

try:
    json.dumps(Klass(10), cls=CustomEncoder) # TypeError: Object of type dict is not JSON serializable
except TypeError:
    print("Error 1")
# this is my end goal to encode a dict of objects
try:
    json.dumps({"Test":Klass(10)}, cls=CustomEncoder) # TypeError: Object of type dict is not JSON serializable
except TypeError:
    print("Error 2")

# this works but clearly it shows me the Custom encoder is not doing what I think it does
encode_hack = {k: v.__dict__() for k, v in {"Test":Klass(10)}.items()}
json.dumps(encode_hack)

CodePudding user response:

You shouldn't define a class method named __dict__, it's a special read-only built-in class attribute, not a method, and you don't need to overload in order to do what you want.

Here's a modified version of your code that show how to do things:

import json

class Klass:
    def __init__(self,number):
        self.number = number

# Don't do this.
#    def __dict__(self):
#        return {"number": self.number}

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Klass):
#            obj=obj.__dict__()
             return {"number": obj.number}
        return json.JSONEncoder.default(self, obj)

json.dumps({"number" : 10})                         # works
json.dumps({"number" : 10}, cls=json.JSONEncoder)   # works
json.dumps({"number" : 10}, cls=CustomEncoder)      # works
json.dumps(Klass(10).__dict__, cls=CustomEncoder) # works
json.dumps({"Test":Klass(10).__dict__}, cls=CustomEncoder) #works

try:
    json.dumps(Klass(10), cls=CustomEncoder)
except TypeError as exc:
    print(exc)

# this is my end goal to encode a dict of objects
try:
    json.dumps({"Test":Klass(10)}, cls=CustomEncoder)
except TypeError as exc:
    print(exc)

# this works but clearly it shows me the Custom encoder is not doing what i think it does
encode_hack = {k: v.__dict__ for k, v in {"Test":Klass(10)}.items()}
json.dumps(encode_hack)

Update

An even better way in my opinion to do it would be to rename your __dict__() method to something not reserved and have your custom encoder call it. A major advantage being that now it's now more object-oriented and generic in the sense that any class with a method of that name could also be encoded (and you don't have to hardcode a class name like Klass in your encoder).

Here's what I mean:

import json

class Klass:
    def __init__(self,number):
        self.number = number

    def _to_json(self):
        return {"number": self.number}


class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        try:
            return obj._to_json()
        except AttributeError:
            return super().default(obj)

# A single object.
try:
    print(json.dumps(Klass(10), cls=CustomEncoder))  # -> {"number": 10}
except TypeError as exc:
    print(exc)

# A dict of them.
try:
    print(json.dumps({"Test":Klass(42)}, cls=CustomEncoder)) # -> {"Test": {"number": 42}}
except TypeError as exc:
    print(exc)

CodePudding user response:

Dunder names are reserved for use by the implementation. Don't reuse them outside their documented usage, and don't invent your own.

import json

class Klass():
    def __init__(self,number):
        self.number = number

    def to_dict(self):
        return {"number": self.number}


class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Klass):
            return obj.to_dict()
        return json.JSONEncoder.default(self, obj)
  • Related