JSON does not accept binary data. Thus, when you want to embed binary data in a request (for example a form with regular fields + an image), these are the options you have available:

  • Encode the image in Base64 (string representation) and send it with the rest of the fields. This makes you incur some overhead in terms of bytes over the wire and computation: Binary Data in JSON String. Something better than Base64 - SO.
  • Use a modern binary serialization format, such as MessagePack. This is a cool approach but requires the client to obviously implement this protocol.DRF - MessagePack.
  • Sending both the fields and the binary data as a multipart form. DRF already accepts binary data as the input for the serializers.ImageField. The only thing you need to add are the parser classes parser_classes = (MultiPartParser, FormParser, JSONParser) in the viewsets that DRF will try to use (or even globally) Parsers - DRF. However, you cannot have nested structures (you can have arrays, however, by adding to the form every value of the array with the same key). Similar to the following code snippet (although that code snippet only encodes the binary fields, but for the rest of the fields is similar).
/**
 * Checks all keys in data looking for binary files (in this case identified by having the 'rawFile' property).
 * If it finds at least one, it prepares a multipart PATCH request exclusively for the binary data.
 * @returns options The httpClient `option` parameter (containing method, headers and body of the request).
 */
function convertToMultipartRequestIfFile(data: any) {
  const form = new FormData();
  let hasBinaryData = false;
  Object.keys(data).forEach((fieldName) => {
    let fieldValue = data[fieldName];
    if (!fieldValue) {
      return;
    }
    if (fieldValue.hasOwnProperty('rawFile')) {
      fieldValue = fieldValue.rawFile;
      hasBinaryData = true;
      form.append(fieldName, fieldValue);
      delete data[fieldName];
    }
  });
  const options: Record<string, any> = {};
  if (hasBinaryData) {
    options.method = 'PATCH';
    options.headers = new Headers({
      'Accept-Language': getLocale(),  // We indicate Django the language we are interested with user prefs.
    });
    options.body = form;
  }
  return options;
}
 

If you need nested structures and binary data, an alternative is to send two requests: one with the JSON data, and a multipart form for the binary data.