Reputation: 56
I'm using the enum34 package and have managed to store objects subclassing Enum to json in a custom format. However, I have an object instance used quite a bit that subclasses IntEnum which doesn't seems to be picked up correctly by my custom JSONEncoder.
My last resort was to convert it to an Enum and refactor all related code accordingly. The advantage of IntEnum is that I could do bitwise operations/tests.
Following is an example and the custom Encoder. Note that Enum works, but IntEnum I assume is being caught before it is passed on to default function.
class TimeMode(Enum):
NTSC = 0
PAL = 1
Film = 2
class UiFlags(IntEnum):
Empty = 0
Clear = 0
Checked = 1
Selected = 2
Shared = 8
class CObjectsEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, IntEnum):
return {'__class__': o.__class__.__name__,
'__value__': (o.value,)}
if isinstance(o, Enum):
return {'__class__': o.__class__.__name__,
'__value__': (o.value,)}
return json.JSONEncoder.default(self, o)
time_mode = TimeMode.NTSC
ui_flags = UiFlags.Checked
CObjectsEncoder().encode(time_mode)
CObjectsEncoder().encode(ui_flags)
result:
'{"__class__": "TimeMode", "__value__": [0]}' # correct encoding
'UiFlags.Checked' # incorrect encoding
Is there a way I can get the same result for an IntEnum? I assume that it's being converted to str upfront like it would an int. How can I get the encoder to pick up IntEnum?
Upvotes: 3
Views: 1981
Reputation: 7756
I worked around this behavior by implementing __str__
on my IntEnum class:
class UiFlags(IntEnum):
Empty = 0
Clear = 0
def __str__(self):
return str(self.value)
json.dumps({'empty': UiFlags.Empty, 'clear': UiFlags.Clear})
# {"empty": 0, "clear": 1}
Upvotes: 1
Reputation: 1123400
The problem here is that isinstance(ui_flags, int)
is true, which means that the standard encoding for integers is used. Your custom encoder is never called, because the built-in encoder 'knows' how to handle integers:
>>> isinstance(UiFlags.Checked, IntEnum)
True
>>> isinstance(UiFlags.Checked, int)
True
There is work-around for this at the JSONEncoder
level; you could pre-process your structure:
def map_intenum(data):
if isinstance(data, IntEnum):
return {'__class__': o.__class__.__name__,
'__value__': (o.value,)}
if isinstance(data, dict):
return {k: map_intenum(v) for k, v in data.iteritems()}
if isinstance(data, (list, tuple)):
return [map_intenum(v) for v in data]
return data
and applying that to your data-to-be-encoded before encoding to JSON:
json.dumps(map_intenum(data), cls=CObjectsEncoder)
The next step would be to override JSONEncoder.iterencode()
and apply the transformation before encoding:
class CObjectsEncoder(json.JSONEncoder):
def iterencode(self, o, _one_shot=False):
o = map_intenum(o)
return super(CObjectsEncoder, self).iterencode(o, _one_shot)
def default(self, o):
if isinstance(o, Enum):
return {'__class__': o.__class__.__name__,
'__value__': (o.value,)}
return super(CObjectsEncoder, self).default(o)
Upvotes: 4