ramonhagenaars / jsons

🐍 A Python lib for (de)serializing Python objects to/from JSON
https://jsons.readthedocs.io
MIT License
289 stars 41 forks source link

Dicts with non-json keys (e.g. Enums) could be dumped more nicely. #140

Open rupert-madden-abbott opened 3 years ago

rupert-madden-abbott commented 3 years ago

Example

class WeekDay(Enum):
    MONDAY = "MONDAY"
    TUESDAY = "TUESDAY"
    WEDNESDAY = "WEDNESDAY"
    THURSDAY = "THURSDAY"
    FRIDAY = "FRIDAY"
    SATURDAY = "SATURDAY"
    SUNDAY = "SUNDAY"

example = {
  WeekDay.MONDAY: 10,
  WeekDay.SUNDAY: 1
}

jsons.dump(example)

Expected Output

{
  "MONDAY": 10,
  "SUNDAY": 1
}

Actual Output The enum keys are hashed and the hashes are used in place of the actual keys whilst an additional "-keys" map is added with the hashes back to the original values.

swissguest commented 3 years ago

I have a similar scenario (mixed value types only for experimentation).

class WeekDay(Enum):
    MONDAY = "MON"
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    SUNDAY = 7

example: Dict[WeekDay, int]
example = {
    WeekDay.MONDAY: 10,
    WeekDay.SUNDAY: 1
}

print(example)
dumped = jsons.dumps(example, strict=True, use_enum_name=False)
print(dumped)

deserialized = jsons.loads(dumped, Dict[WeekDay, int], use_enum_name=False)
print(deserialized)

De-serialization of the serialized form does work as expected:

{<WeekDay.MONDAY: 'MON'>: 10, <WeekDay.SUNDAY: 7>: 1}
{"-keys": {"6161275848482540713": "MON", "7973924388498358727": 7}, "6161275848482540713": 10, "7973924388498358727": 1}
{<WeekDay.MONDAY: 'MON'>: 10, <WeekDay.SUNDAY: 7>: 1}

Admittedly, the serialized form of a dict with Enum keys does look a little odd. But it seems they need to make sure the keys are valid types for Python's JSON. Search the code for "if not any(issubclass(type(key), json_key) for json_key in JSON_KEYS)".

I think you should rephrase the issue's title into "A dict with Enum keys serialized representation does not look the way I like". This way, you can argue the beauty of the representation (along the lines "readability counts", compatibility with external systems, use of de-serialization libs other than JSON). But for as long as you want to dump into JSON format, you need to make sure that the keys are of acceptable types.

rupert-madden-abbott commented 3 years ago

@swissguest

Admittedly, the serialized form of a dict with Enum keys does look a little odd. But it seems they need to make sure the keys are valid types for Python's JSON.

Why is that? Enums are serialised to strings when they are not dict keys.

ramonhagenaars commented 3 years ago

I think swissguest answered it nicely. 💐

Python allows more types as keys in dicts than json allows keys in objects. As you know, anything that is hashable in Python can be used as a key in a dict, whereas json allows only strings, integers, floats, booleans or null.

jsons uses the object's hash to still be able to dump dicts with keys that would not be valid in json. Enumerations are no exception to this, that's why you get the output you described.

I'm not against making an exception for enumerations though, as I agree that your expected outcome would be nicer to read. However, it would encompass also featuring a new argument to the load function. Because upon loading, you would need to somehow tell jsons to load the keys of the source into your enum. Something like this:

jsons.load(dumped, cls=SomeClass, key_cls=SomeEnum)