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

Question about JsonSerializable.json method #173

Open nightan42643 opened 2 years ago

nightan42643 commented 2 years ago

Hey

When I defined a dataclass as a subclass of JsonSerializable, I can access its json method. But json method returns an object

@dataclass
class Person(JsonSerializable
                .with_dump(key_transformer=KEY_TRANSFORMER_CAMELCASE)
                .with_load(key_transformer=KEY_TRANSFORMER_SNAKECASE)):
    first_name: str
    last_name: str

arno = Person('Arno','Y')
arno_json = arno.json

arno_json is an object that lint thinks. If I want let lint think it is a dict, I need to ...

if isinstance(arno_json, dict):
    print(arno_json.keys())

I am not sure if my understanding was correct: If I use JsonSerializable like above, I mean create a subclass of it. In this case, the object json returned always is a dict? But I must do an isInstance judgment, it looks a little weird.

Is it possible to let the json method return a dict not an object

ramonhagenaars commented 2 years ago

Hi @nightan42643,

The reason for .json to return an object and not a dict is a bit theoretical. One could be writing a class that is not to be represented as a dict when turned into json, for example a custom collection class:

class MyCollection(list, JsonSerializable):
    ...

c = MyCollection((1, 2, 3))
c.json  # <-- this results in a list, not a dict.

MyCollection could not override .json to hint the right type (list instead of dict) as that would be a violation of the Liskov Substitution Principle. But since JsonSerializable hints .json as returning an object, it is possible to override and hint list (or dict in your case) since that is a subtype of object. Liskov can rest assured.

In your case, you could override as follows:

@dataclass
class Person(JsonSerializable
                .with_dump(key_transformer=KEY_TRANSFORMER_CAMELCASE)
                .with_load(key_transformer=KEY_TRANSFORMER_SNAKECASE)):
    first_name: str
    last_name: str

    @property
    def json(self) -> dict[str, str]:
        return JsonSerializable.json.fget(self)

Or maybe even like this:

@dataclass
class Person(JsonSerializable
                .with_dump(key_transformer=KEY_TRANSFORMER_CAMELCASE)
                .with_load(key_transformer=KEY_TRANSFORMER_SNAKECASE)):
    first_name: str
    last_name: str

    json: dict[str, str] = field(init=False, repr=False, compare=False)

Alternatively, you could use typing.cast:

arno_json = typing.cast(dict[str, str], arno.json)

I hope this helps.

nightan42643 commented 2 years ago

@ramonhagenaars Very clear, thanks!