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

Add support for decorating methods with specific serialization properties #138

Open
cableray opened this issue Jul 22, 2021 · 3 comments
Open
Labels
feature A new feature that awaits implementation

Comments

@cableray
Copy link

It would be nice to modify JSON serialization for class instances in a more flexible manner. I have been hoping for something that could be used like this:

class Foo:
  @property
  @json_attribute(key='differentJson_key', serializer=some_serializer, deserializer=some_deserializer)
  def bar(self):
    return 'something'

  @bar.setter
  def bar(self, value):
    do_something_with(value)

  @property
  @json_attribute(skip=True)
  def python_only_prop(self):
    return 'something that only makes sense in python'

  # Multiple args could be supported, but the more common use case is a callable getter that takes no args
  @json_attribute(key='computed_result', serialize_with_args=(1,2) or (lambda self: (1,2)))
  def get_computation(self, a, b):
    return f"{a} & {b}"

Something like this gives more flexible and declarative customization. This pattern would also allow smaller refactoring, allowing adding just a few json properties at a time (if you could set skip to True by default, if you wanted...).

I'd love to help with an implementation, and I'm trying to figure one out on my own. Playing nice with other descriptors is the hard part, as descriptors are not very composable...

@ramonhagenaars ramonhagenaars added the feature A new feature that awaits implementation label Jul 29, 2021
@ramonhagenaars
Copy link
Owner

Hi @cableray,

I like your idea! It allows one to influence the serialization/deserialization without access to the actually call of dump. Or in other words: it allows one to conveniently decentralize the knowledge of how to dump/load classes.

Some of the features you describe can already be done (e.g. jsons.dump(strip_attr='python_only_prop')). I'd like them to be implemented first, as the primary use of jsons should be dumping regular Python (data)classes without compromising them.

The other features (e.g. a dedicated serializer for an attribute) can be added later, as soon as it has been added to the "standard" jsons dump (e.g. something like: jsons.dump(some_obj, serializers=[('SomeClass.some_attr', custom_serializer)])).

Would you agree?

@cableray
Copy link
Author

cableray commented Aug 9, 2021

Yeah, makes sense. Looking into it more, and trying to implement something on my own, I'm kinda thinking there needs to be some sort of configurable serializer builder object that can handle this, and it can be a class property (probably a callable/descriptor):

class Foo:
  serializer = Serializer(
    strip_attr='serializer',  # although it would probably strip itself by default
    serializers=[('some_attr', custom_serializer)]
  )

  def __init__(self):
    serializer.some_option = "some dynamic value"

  @serializer.serialize(…)
  def  get_computation(self, a, b):
    return f"{a} & {b}"

Using a builder object allows some configuration when creating the builder, some configuration using decorators, and even some configuration during init, if you fork a serializer instance for each object instance (using a descriptor that works like how functions bind on __get__)

Trying to implement it myself, I'm seeing how it gets tricky quick, because of a lot of edge cases with descriptors and decorators -- they're not always exactly composable the way I wish.

@dantebarba
Copy link

I think I'm currently needing this feature.

Currently I use jsons to transform from an application model to dtos. Unfortunately I have the case where the model may have attributes that need to be transformed in order to be converted into dtos.

e.g one of the Models has two attributes that are filled depending on the api call, vendor_name and account_name. But in the dtos we currently only want to load the account_name, that means if the vendor_name gets filled it should be returned as account_name too. This is easy using the dedicated serializer decorators, something like:

class Foo:
  @property
  @json_attribute(key='account_name', serializer=some_serializer, deserializer=some_deserializer)
  def vendor_name(self):
    return 'something'

Maybe there is already a simple solution to rename attributes when deserializing but I couldn't find a way without re-writing the whole deserializer function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature A new feature that awaits implementation
Projects
None yet
Development

No branches or pull requests

3 participants