Repository: strawberry-graphql/strawberry

Issue:
Expose `dataclasses.asdict` to `strawberry.asdict` and document it
A common use case is to convert types to dicts, which we already support through dataclasses.asdict. 

We should expose `asdict` via the strawberry namespace, so we can make sure there's always a supported way to convert types to dictionaries. And we can also add additional code checks if needed :)

Example here: https://play.strawberry.rocks/?gist=c8e729c52efef8b75f1599c759c069c8


Discussion:
1. should the `asdict` resolve the child types as dict as well?
2. what would be the API?
```py
@strawberry.type
class Foo:
    bar: str

d = Foo("baz").asdict()
```
vs
```py
d = asdict(Foo("baz"))
```
vs
```py
d = dict(Foo("baz"))
```
3. Should we do type checks while casting to dict? 
4. what should we do with fields with resolvers?

This seems helpful and would like to know if it would support resolvers.  Here is a simple example:

```
from dataclasses import asdict

import strawberry

@strawberry.type
class Position:
    trading_pl: float
    position_pl: float

    @strawberry.field
    def desk_pl(self) -> float:
        return self.trading_pl + self.position_pl

dict_data = asdict(Position(1, 2))
```

In the above example, dict_data["desk_pl"] will be a method and you have to call it to get the result, e.g.:

```
dict_data["desk_pl"] = dict_data["desk_pl"]()
```

The same issue exists with dataclasses support in Pandas.  The same behavior occurs with pd.DataFrame([Position(1,2)]).

I can see it may be challenging to implement built-in behavior to support all scenarios.

In my examples, I would to avoid hardcoding calls to the methods generated from resolvers.  Does Strawberry expose a public API to get the list of resolvers for a given type?
@bperkins24 it's not really possible to support resolvers in any kind of strawberry type serialisation because resolvers have to be called in the context of a GraphQL execution since they can rely on the `info` object: https://strawberry.rocks/docs/types/resolvers#accessing-execution-information
When I using `dataclasses.asdict(...)` PyCharm or other lint always said "asdict() should be called on dataclass instances". So I really agreed with @patrick91's feature request to expose `strawberry.asdict(...)`.

I want to try to solve this issue. I'm newcomer of strawberry's internal. But... Can I try this?
@Hazealign sure! I think we can start by exposing a function called asdict that calls dataclasses.asdict, then we can improve on it later :)

```python
def asdict(o: object) -> dict:
    return dataclasses.asdict(o)
```

something like this :)

Resolving patch:
diff --git a/strawberry/__init__.py b/strawberry/__init__.py
--- a/strawberry/__init__.py
+++ b/strawberry/__init__.py
@@ -7,7 +7,7 @@
 from .field import field
 from .lazy_type import LazyType, lazy
 from .mutation import mutation, subscription
-from .object_type import input, interface, type
+from .object_type import asdict, input, interface, type
 from .permission import BasePermission
 from .private import Private
 from .scalars import ID
@@ -41,4 +41,5 @@
     "type",
     "union",
     "auto",
+    "asdict",
 ]
diff --git a/strawberry/object_type.py b/strawberry/object_type.py
--- a/strawberry/object_type.py
+++ b/strawberry/object_type.py
@@ -27,6 +27,8 @@
 from .utils.str_converters import to_camel_case
 from .utils.typing import __dataclass_transform__
 
+T = TypeVar("T", bound=Type)
+
 
 def _get_interfaces(cls: Type) -> List[TypeDefinition]:
     interfaces = []
@@ -179,9 +181,6 @@ def _process_type(
     return cls
 
 
-T = TypeVar("T", bound=Type)
-
-
 @overload
 @__dataclass_transform__(
     order_default=True, kw_only_default=True, field_descriptors=(field, StrawberryField)
@@ -364,9 +363,25 @@ def interface(
     )
 
 
+def asdict(obj: object) -> Dict[str, object]:
+    """Convert a strawberry object into a dictionary.
+    This wraps the dataclasses.asdict function to strawberry.
+
+    Example usage:
+    >>> @strawberry.type
+    >>> class User:
+    >>>     name: str
+    >>>     age: int
+    >>> # should be {"name": "Lorem", "age": 25}
+    >>> user_dict = strawberry.asdict(User(name="Lorem", age=25))
+    """
+    return dataclasses.asdict(obj)
+
+
 __all__ = [
     "TypeDefinition",
     "input",
     "interface",
     "type",
+    "asdict",
 ]


Test patch:
diff --git a/tests/types/test_convert_to_dictionary.py b/tests/types/test_convert_to_dictionary.py
new file mode 100644
--- /dev/null
+++ b/tests/types/test_convert_to_dictionary.py
@@ -0,0 +1,65 @@
+from enum import Enum
+from typing import List, Optional
+
+import strawberry
+from strawberry import asdict
+
+
+def test_convert_simple_type_to_dictionary():
+    @strawberry.type
+    class People:
+        name: str
+        age: int
+
+    lorem = People(name="Alex", age=30)
+
+    assert asdict(lorem) == {
+        "name": "Alex",
+        "age": 30,
+    }
+
+
+def test_convert_complex_type_to_dictionary():
+    @strawberry.enum
+    class Count(Enum):
+        TWO = "two"
+        FOUR = "four"
+
+    @strawberry.type
+    class Animal:
+        legs: Count
+
+    @strawberry.type
+    class People:
+        name: str
+        animals: List[Animal]
+
+    lorem = People(
+        name="Kevin", animals=[Animal(legs=Count.TWO), Animal(legs=Count.FOUR)]
+    )
+
+    assert asdict(lorem) == {
+        "name": "Kevin",
+        "animals": [
+            {"legs": Count.TWO},
+            {"legs": Count.FOUR},
+        ],
+    }
+
+
+def test_convert_input_to_dictionary():
+    @strawberry.input
+    class QnaInput:
+        title: str
+        description: str
+        tags: Optional[List[str]] = strawberry.field(default=None)
+
+    title = "Where is the capital of United Kingdom?"
+    description = "London is the capital of United Kingdom."
+    qna = QnaInput(title=title, description=description)
+
+    assert asdict(qna) == {
+        "title": title,
+        "description": description,
+        "tags": None,
+    }