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)