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

Way to serialize/deserialize nested items and union type #3637

Open
1 of 3 tasks
tobiasBora opened this issue Sep 21, 2024 · 2 comments
Open
1 of 3 tasks

Way to serialize/deserialize nested items and union type #3637

tobiasBora opened this issue Sep 21, 2024 · 2 comments

Comments

@tobiasBora
Copy link

tobiasBora commented Sep 21, 2024

Feature Request Type

  • Core functionality
  • Alteration (enhancement/optimization) of existing feature(s)
  • New behavior

Description

I need to store/read data from a redis database (I guess it is a fairly common usecase), and therefore the objects need to be serialized to store them, and un-serialize to recover them later. But I can't find really nice generic way to do that. For now I'm using

def serialize_dataclass(obj, kind):
    if not kind:
        kind = obj.__name__
    return json.dumps({**asdict(obj), "__typename": obj.__name__})

def deserialize_dataclass(json):
    ## TODO: not very secure, see later
    return globals()[json["__typename"]](**{k:v for k,v in data.items() if k != "__typename"})

but this is not great for multiple reasons:

  • the main issue is that I don't think this will work for nested types (e.g. a Person has an age, a name… and aCard, where card is itself another type)
  • secondly, if a type is a union (say type Card = Number | Figure), I need to store in the database which precise sub-type (eg. Figure) the original object is (this is fine), but when I restore it later, I use globals() to go from the name of the type to the type itself… but this is not very secure as I can't really be sure that the new type is indeed a subtype of the original one. For instance if the database was corrupted, I might read __typename = MaliciousFigure, and then this would create a MaliciousFigure instead of a Figure… For safety/debugging purposes, I'd prefer to raise an error if the type is not part of Card, but this seems not trivial to do (at least I just found get_origin and get_args for the typing library, but this is quite low level and require non-negligible of work to use to recover the precise sub-types for more complicated types)

Am I missing something obvious? How are people dealing with this? If there is no easy solution here, I would love to have it in the library by default.

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar
@tobiasBora
Copy link
Author

Note that using pydantic, we can serialize/deserialize in a better way… but strawberry still does not really conversion from pydantic types for union types (and the recursive translation seems also quite annoying, if at all possible) #3641

@Corentin-Bravo
Copy link

I have trouble understanding your problem and how it ties into strawberry.

Your issue seems to be with serializing and deserializing from your datastore. That is not strawberry domain. Do your serialization and deserialization however you want/need.

Strawberry only matters when you're communicating outside of your application.

Let me illustrate it

class MyInternalClass:
   some_attr: str
   some_int_attr: int
   some_attr_that_definitely_should_not_be_exposed_but_I_need_it: str

@strawberry.type
class MyReturnClass:
   some_attr: str
   some_int_attr: int

def my_route() -> MyInternalClass:
    my_internal_class_instance = get_data_from_datastore() # How you do it is your business
    return my_internal_class_instance


@strawberry.type
class Query:
   my_route_query = strawberry.field(resolver=my_route, graphql_type=MyReturnClass)

If you call my_route_query you will receive {"someAttr": ..., "someIntAttr": ...} and the fact that you technically had other attributes is entirely irrelevant to strawberry.

As you see the two types do not even have any inheritance relation. Strawberry just looks at what you give it and see if it can serialize into the type you asked it to.
My example is pretty trivial, but you can have nested classes, union types, whatever you want, it just works, because strawberry types are basically just JSON schemas.

You may want to be able to avoid defining both an internal and return class, especially if you have very deeply nested types as it would involve a lot of boilerplate, however this problem is for the app to solve, not strawberry.

My humble opinion is that you should look for an ORM to help you with your serialization and deserialization problems. Once you do that, you may be able to extend it to help you generate your strawberry classes in a DRY way.

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

2 participants