Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip default value members on serialization #11

Closed
atteneder opened this issue Oct 16, 2019 · 8 comments
Closed

Skip default value members on serialization #11

atteneder opened this issue Oct 16, 2019 · 8 comments

Comments

@atteneder
Copy link

Hi!

Is there a way to skip variables that are default values from serialization/dict conversion?

exampe:

@dataclass
class MyClass(DataClassJSONMixin):
    name: str = None
    soul: str = None


c = MyClass()
c.name='itseme'

print(c.to_json())

output: {"name": "itseme", "soul": null}
desired output : {"name": "itseme"}

thanks!

@atteneder
Copy link
Author

atteneder commented Oct 16, 2019

This work-around does not work on nested member/classes though:

import json
from typing import Optional, Mapping
from types import MappingProxyType
from mashumaro import DataClassJSONMixin
from mashumaro.serializer.json import Encoder,EncodedData,DEFAULT_DICT_PARAMS,T

@dataclass
class MyClass(DataClassJSONMixin):
    name: str
    soul: str = None

    def to_json(
        self: T,
        encoder: Optional[Encoder] = json.dumps,
        dict_params: Optional[Mapping] = MappingProxyType({}),
        **encoder_kwargs) -> EncodedData:

        dct = self.to_dict(**dict(DEFAULT_DICT_PARAMS, **dict_params))

        for key,f in self.__class__.__dataclass_fields__.items():
            if dct[key] == f.default:
                dct.pop(key)

        return encoder(
            dct,
            **encoder_kwargs
        )

c=MyClass('itseme')
print(to_json())

@Fatal1ty
Copy link
Owner

Hi @atteneder

Your solution seems to work with nested classes as well.

@dataclass
class MyClassChild(MyClass, DataClassJSONMixin):
    another: str = ''

c = MyClassChild('itseme')
print(c.to_json())  # {"name": "itseme"}


@dataclass
class Parent(DataClassJSONMixin):
    a: str
    b: str = None

    def to_json(
        self: T,
        encoder: Optional[Encoder] = json.dumps,
        dict_params: Optional[Mapping] = MappingProxyType({}),
        **encoder_kwargs) -> EncodedData:

        dct = self.to_dict(**dict(DEFAULT_DICT_PARAMS, **dict_params))

        for key,f in self.__class__.__dataclass_fields__.items():
            if dct[key] == f.default:
                dct.pop(key)

        return encoder(
            dct,
            **encoder_kwargs
        )

@dataclass
class Child(Parent, DataClassJSONMixin):
    c: str = None

c = Child('a')
print(c.to_json())  # {"a": "a"}

Could you write an example of what doesn't work?

@atteneder
Copy link
Author

Sure!

What I meant was nested dataclasses:

import json
from dataclasses import dataclass
from typing import Optional, Mapping
from types import MappingProxyType
from mashumaro import DataClassJSONMixin
from mashumaro.serializer.json import Encoder,EncodedData,DEFAULT_DICT_PARAMS,T

@dataclass
class SkipperBase(DataClassJSONMixin):
    def to_json(
        self: T,
        encoder: Optional[Encoder] = json.dumps,
        dict_params: Optional[Mapping] = MappingProxyType({}),
        **encoder_kwargs) -> EncodedData:

        dct = self.to_dict(**dict(DEFAULT_DICT_PARAMS, **dict_params))

        for key,f in self.__class__.__dataclass_fields__.items():
            if dct[key] == f.default:
                dct.pop(key)

        return encoder(
            dct,
            **encoder_kwargs
        )

@dataclass
class MyClass(SkipperBase):
    name: str
    soul: str = None

@dataclass
class MyParentClass(SkipperBase):
    child: MyClass = None

p=MyParentClass()
p.child = MyClass('itseme')
print(p.to_json())

output: {"child": {"name": "itseme", "soul": null}}
desired output: {"child": {"name": "itseme"}}

That's because the child is converted to a dict (with all default params as well) and serialized to JSON within the parent's to_json.

I guess I could override the to_dict (so that it skips default members), but that's an odd default behaviour. Ideally to_dict has a parameter for skipping default values that gets propagated to nested members.

What do you think?

@Fatal1ty
Copy link
Owner

I think the additional parameter in to_dict will solve the problem perfectly. But we need to check how much it will affect performance. Unfortunately, I don’t have enough time the next few days. It would be great if you made changes and opened a pull request. But if you don’t, I will try to find time to check it out.

@rominf
Copy link

rominf commented May 9, 2020

Any updates on this? I need this too.

@atteneder
Copy link
Author

Sorry, seems I won't have time to tinker with this.
I've been using dataclasses-json as an alternative lately. Maybe it works for this case as well.

@Fatal1ty
Copy link
Owner

It's possible to skip None values with the optional omit_none code generation flag:

from dataclasses import dataclass

from mashumaro import DataClassJSONMixin
from mashumaro.config import TO_DICT_ADD_OMIT_NONE_FLAG

@dataclass
class MyClass(DataClassJSONMixin):
    name: str = None
    soul: str = None

    class Config:
        code_generation_options = [TO_DICT_ADD_OMIT_NONE_FLAG]

c = MyClass()
c.name = 'itseme'

print(c.to_json(dict_params=dict(omit_none=True)))  # {"name": "itseme"}

At the moment this flag exists in the master branch but it will be released in the next version.

@atteneder
Copy link
Author

Thank you very much for offering a solution!

To me this is satisfactory. It seems to not work for non-None default values, however.

@Fatal1ty Feel free to close the issue or keep it open for said shortcoming.

Again, thanks for this library!

@Fatal1ty Fatal1ty closed this as completed Mar 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants