strawberry icon indicating copy to clipboard operation
strawberry copied to clipboard

Input type is not JSON serializable

Open mecampbellsoup opened this issue 3 years ago • 13 comments

Hi @patrick91 -

I'm at a loss for what is going on here.

I've defined an object input type SubscriptionForm which I am using in a mutation resolver as follows:

@strawberry.input
class PaymentProfileAttributes:
    first_name: str
    last_name: str
    full_number: str
    cvv: str
    expiration_month: str
    expiration_year: str
    billing_zip: str


@strawberry.input
class CustomerAttributes:
    first_name: str
    last_name: str
    email: str
    phone: Optional[str]
    organization: str
    reference: str


@strawberry.input
class SubscriptionForm:
    agree_to_terms: bool
    payment_profile_attributes: PaymentProfileAttributes
    customer_attributes: CustomerAttributes
    product_handle: str
    reference: str

@strawberry.type
class Mutation:
    @strawberry.mutation
    async def subscribe(self, info: Info, form: SubscriptionForm) -> APIResponse:
        ...

However the graphql server is erroring with:

{"data":null,"errors":[{"message":"Object of type SubscriptionForm is not JSON serializable","locations":[{"line":2,"column":3}],"path":["subscribe"]}]}

The object being sent to the server looks as follows:

{
    "subscription": {
        "agreeToTerms": false,
        "paymentProfileAttributes": {
            "firstName": "",
            "lastName": "",
            "fullNumber": "",
            "cvv": "",
            "expirationMonth": "",
            "expirationYear": "",
            "billingZip": ""
        },
        "customerAttributes": {
            "firstName": "",
            "lastName": "",
            "email": "[email protected]",
            "phone": "",
            "organization": "44a18ce7b0",
            "reference": "[email protected]"
        },
        "productHandle": "coreweave-cloud",
        "reference": "tenant-ef1b7290f0"
    }
}

Am I missing something obvious here?

mecampbellsoup avatar Mar 19 '21 20:03 mecampbellsoup

@BryceBeagle @jkimbo maybe you guys can grok what I'm doing wrong?

mecampbellsoup avatar Mar 19 '21 20:03 mecampbellsoup

@mecampbellsoup I've tried this with both starlette and django, it seems to be working fine.

maybe there's something in your resolver that's causing the issue? or do you have a custom django view?

patrick91 avatar Mar 19 '21 22:03 patrick91

@patrick91 here is my resolver:

@strawberry.type
class APIResponse:
    ok: bool
    errors: List[Optional[Error]]

@strawberry.type
class Mutation:
    @strawberry.mutation
    async def subscribe(self, info: Info, form: SubscriptionForm) -> APIResponse:
        form_data = form
        base_url = os.getenv('CHARGIFY_API_URL')
        async with httpx.AsyncClient(base_url=base_url) as client:
            api_key = os.getenv('CHARGIFY_API_KEY')
            auth_str = base64.b64encode(f'{api_key}:x'.encode('utf-8')).decode('utf-8')
            headers = {
                'Authorization': f'Basic {auth_str}',
                'Content-Type': 'application/json'
            }
            resp: HttpxResponse = await client.post('/subscriptions.json', headers=headers, json=form_data)
            status: int = resp.status_code
            print(status)
            body: dict = resp.json()
            print(body)

mecampbellsoup avatar Mar 19 '21 23:03 mecampbellsoup

oh yes, this is the line that's broken:

resp: HttpxResponse = await client.post('/subscriptions.json', headers=headers, json=form_data)

you can do instead (after importing dataclasses:

resp: HttpxResponse = await client.post('/subscriptions.json', headers=headers, json=dataclasses.asdict(form_data))

patrick91 avatar Mar 19 '21 23:03 patrick91

OMG, thank you so much Patrick!!

How did you know that was the issue? Is this something I could add to the docs?

mecampbellsoup avatar Mar 19 '21 23:03 mecampbellsoup

OMG, thank you so much Patrick!!

How did you know that was the issue? Is this something I could add to the docs?

I've seen that error a few times :) but I think also we haven't really documented well the fact that we can convert strawberry types to dicts with dataclasses.asdict (it's implicit by the fact we use dataclasses). I wonder if we can do something better here (we could have our own asdict function or maybe a method on the types as well) 🤔

patrick91 avatar Mar 19 '21 23:03 patrick91

We might just want to implement our own JSONEncoder that knows how to deal with Strawberry types

BryceBeagle avatar Mar 20 '21 00:03 BryceBeagle

@BryceBeagle do you prefer that to converting types to dicts?

patrick91 avatar Mar 20 '21 00:03 patrick91

Probably, but only marginally

BryceBeagle avatar Mar 20 '21 00:03 BryceBeagle

Converting things to dicts might retain types like dates and so on, which might be useful in somecases (also it should be faster than converting things to json all the time).

patrick91 avatar Mar 20 '21 00:03 patrick91

But here, the use case is explicitly for json, so I think it would be best to do the recursive conversion all the way down the object tree

BryceBeagle avatar Mar 20 '21 00:03 BryceBeagle

But here, the use case is explicitly for json, so I think it would be best to do the recursive conversion all the way down the object tree

httpx will do the actual json conversion here: https://github.com/encode/httpx/blob/a3eb0f99dc112fb5b09c7d6fdb9d211f96a6e3c1/httpx/_content.py#L175

the json parameter expects a dict to be converted to json 😊

patrick91 avatar Mar 20 '21 00:03 patrick91

#2170

nrbnlulu avatar Sep 20 '22 10:09 nrbnlulu