vitalik / django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
https://django-ninja.dev
MIT License
6.96k stars 420 forks source link

How to return nested dict? response={200: Dict... #576

Open ssandr1kka opened 1 year ago

ssandr1kka commented 1 year ago

So right now I have response={200: Dict, codes_4xx: DefaultMessageSchema} I am working on stripe integration, thus I cannot use a defined Schema, I need to pass stripe data. Their dict is nested and quite a big one... It goes something like:

This is stripe data and also expected data which I want to be getting as a response
{
  "data": [
    {
      "billing_details": {
        "address": {
          "city": null,
          "country": null,
          "line1": null,
          "line2": null,
          "postal_code": null,
          "state": null
        },
        "email": null,
        "name": null,
        "phone": null
      },
      "card": {
        "brand": "visa",
        "checks": {
          "address_line1_check": null,
          "address_postal_code_check": null,
          "cvc_check": "pass"
        },
        "country": "US",
        "exp_month": 9,
        "exp_year": 2023,
        "fingerprint": "zsdqFS23F3Aw",
        "funding": "credit",
        "generated_from": null,
        "last4": "4242",
        "networks": {
          "available": [
            "visa"
          ],

The current way I have defined response, it does not recognize nested dictionaries. Meaning instead of "card" it will return 'card' / instead of list it will return string and etc... How can I make it work?

AliGx97 commented 1 year ago

You can use a a schema for each nested element, for example:

class NetworkField(Schema):
    available: list[str]

class ChecksField(Schema):
    address_line1_check: str = None
    address_postal_code_check: str = None
    cvc_check: str

class CardField(Schema):
    brand: str
    checks: ChecksField

class AddressField(Schema):
    city: str = None
    country: str = None
    line1: str = None
    line2: str = None
    postal_code: str = None
    state: str = None

class BillingDetailsField(Schema):
    address: AddressField
    email: str = None
    name: str = None
    phone: str = None

...

And so on. NOTE: I don't know data types of your fields so made them str , but I just wanted to show the approach

Hope that helped!

vitalik commented 1 year ago

@ssandr1kka

The current way I have defined response, it does not recognize nested dictionaries

Not sure what you mean - but it does go nested items of the dict (you just need to make sure that all nested data is json serializible)

Here is a simple test:

@api.get("/test", response={200: Dict})
def test(request):
    return {
        "data": [
            {
                "billing_details": {
                    "address": {
                        "city": None,
                        "country": None,
                        "line1": None,
                        "line2": None,
                        "postal_code": None,
                        "state": None,
                    },
                    "email": None,
                    "name": None,
                    "phone": None,
                },
                "card": {
                    "brand": "visa",
                    "checks": {"address_line1_check": None, "address_postal_code_check": None, "cvc_check": "pass"},
                },
            }
        ]
    }

it returns a pretty correct JSON response:

CleanShot 2022-09-30 at 17 26 53@2x
ssandr1kka commented 1 year ago

Well yeah schemas might change from their end, that is why I have decided not to go that way. I am looking a way that will address their data changes automatically and convert it into a dict fully... If there no other ways I guess that is the only option

You can use a a schema for each nested element, for example:

class NetworkField(Schema):
    available: list[str]

class ChecksField(Schema):
    address_line1_check: str = None
    address_postal_code_check: str = None
    cvc_check: str

class CardField(Schema):
    brand: str
    checks: ChecksField

class AddressField(Schema):
    city: str = None
    country: str = None
    line1: str = None
    line2: str = None
    postal_code: str = None
    state: str = None

class BillingDetailsField(Schema):
    address: AddressField
    email: str = None
    name: str = None
    phone: str = None

...

And so on. NOTE: I don't know data types of your fields so made them str , but I just wanted to show the approach

Hope that helped!