Fatal1ty / mashumaro

Fast and well tested serialization library
Apache License 2.0
774 stars 45 forks source link

Enable variable serialization with parameters to "to_dict" #110

Closed gshank closed 1 year ago

gshank commented 1 year ago

Is your feature request related to a problem? Please describe. We serialize in a number of different places for different purposes. One purpose is to enable reloading generated objects, the other is to write out json artifacts for user consumption. We'd like to be able to omit some fields in the json artifacts that are included in the serialized objects for reloading. If it was a single object it would be easy to use a custom method, but our structures are pretty deeply nested in some cases and it's difficult to pass along parameters.

Describe the solution you'd like It would be nice to be able to provide a parameter to to_dict, such as: obj.to_dict(option="artifact"), that we could pass up the to_dict() chain and use for removing some dictionary keys from the serialization with particular options.

Describe alternatives you've considered A "to_artifact" method that calls to_dict falls apart when we need to go through nested objects. Setting an environment variable wouldn't work because of threading and multiple executions at the same time.

I've looked over the current list of features and don't see a way of doing what I want, but would be glad to find out there is some way to do this.

Fatal1ty commented 1 year ago

Hi @gshank

Correct me if I missed something. Is it what you're looking for?

class BaseModel(DataClassDictMixin):
    class Config(BaseConfig):
        code_generation_options = [ADD_SERIALIZATION_CONTEXT]

@dataclass
class Bar(BaseModel):
    baz: int

    def __pre_serialize__(self, context: Optional[Dict] = None):
        return self

    def __post_serialize__(self, d: Dict, context: Optional[Dict] = None):
        if context and context.get("omit_baz"):
            d.pop("baz")
        return d

@dataclass
class Foo(BaseModel):
    bar: Bar
    baz: int

    def __pre_serialize__(self, context: Optional[Dict] = None):
        return self

    def __post_serialize__(self, d: Dict, context: Optional[Dict] = None):
        if context and context.get("omit_baz"):
            d.pop("baz")
        return d

foo = Foo(bar=Bar(baz=1), baz=2)
assert foo.to_dict() == {"bar": {"baz": 1}, "baz": 2}
assert foo.to_dict(context={"omit_baz": True}) == {"bar": {}}

We can create a new option like ADD_SERIALIZATION_CONTEXT and use it for passing optional context from to_* methods to serialization hooks and down to to_* methods of nested dataclasses if they also have this option enabled.

gshank commented 1 year ago

That looks like just what I want! Wonderful :)

Fatal1ty commented 1 year ago

Good, I don't see anything complicated here, so I'll do my best to include it in the upcoming release along with discriminated unions I'm currently working on.

gshank commented 1 year ago

The discriminated unions will be useful for us too, since we have custom code to check the value of a particular field in order to choose the right class.

Fatal1ty commented 1 year ago

@gshank

I did what you asked. Do you have any chance to do a final check of the new option before the release? You can install it from this branch:

pip install git+https://github.com/Fatal1ty/mashumaro.git@serialization-context
Fatal1ty commented 1 year ago

Ok, I'm putting together a release. If something pops up, we will fix it in the next version.