django-rest-frameworkdrf-spectacular

drf_spectacular schema generation with custom parser


I'm using a custom parser on my POST endpoint so that I can support file uploads and nested objects in the same endpoint (context):

class MultipartJsonParser(parsers.MultiPartParser):
    def parse(self, stream, media_type=None, parser_context=None):
        result = super().parse(stream, media_type=media_type, parser_context=parser_context)
        return parsers.DataAndFiles(json.loads(result.data["data"]), result.files)

Data is posted as multipart/form-data with a single "data" key containing the full form as a json string, for example:

data: "{\"about\": {\"name\": \"a\"}}"

My serializer is defined like so:

def AboutSerializer(serializers.Serializer):
    name = serializers.CharField()

def FooSerializer(serializers.Serializer):
    about = AboutSerializer()
    # more fields

My drf_spectacular schema shows this endpoint expects an "about" field, but actually the POST request body should be a "data" field containing a JSON string with the "about" field inside of it.

Is there any way to make drf_spectqacular aware of my custom parser, so that the schema reflects that the only key in the form body should be data, and that data should be a JSON-encoded version of my FooSerializer?


Solution

  • I've realized this can't be done in drf_spectacular. However, depending on which schema/api tools you use on the frontend, you may be able to convert to the desired format there. I'm using openapi-fetch for type-aware fetching in typescript, and was able to achieve this with the following bodySerializer:

    export function multipartJsonBodySerializer(body: Record<string, unknown>) {
      const formData = new FormData();
      const jsonData: Record<string, unknown> = {};
      for (const [key, value] of Object.entries(body)) {
        if (value instanceof File) {
          formData.append(key, value);
        } else {
          jsonData[key] = value;
        }
      }
      formData.append("data", JSON.stringify(jsonData));
      return formData;
    }
    
    // Example post:
    const body = {"about": {"name": "a"}}
    POST("/api/endpoint/", {
          body: body,
          bodySerializer: multipartJsonBodySerializer,
    })
    

    The body object is type-checked as usual then it's converted to my custom format by the bodySerializer, before being posted.