Parse json part of a multipart request

I have a route where I need to receive an uploaded image and a json payload, to do that, I’m using multipart/form-data.

The issue is that when I receive the payload in my controller, the json part is not decoded properly, I receive it in string form.

Here is my curl request, as you can see, I am passing the corret content-type for the json payload:

curl -X POST http://localhost:4000/api/qr_code/simple \
         -H "Content-Type:multipart/form-data" \
         -F "file=@logo.svg;type=image/svg+xml" \
         -F "payload={\"my\":\"json\"};type=application/json"

The only way I managed to achieve that was to create a plug myself that do the decode:

defmodule MyParser do
  @moduledoc false
  
  @behaviour Plug

  def init(opts) do
    key = opts |> Keyword.fetch!(:key) |> to_string()

    key
  end

  def call(%{req_headers: req_headers} = conn, key) do
    with {"content-type", content_type} <- List.keyfind(req_headers, "content-type", 0),
         true <- String.starts_with?(content_type, "multipart/form-data") do
      maybe_parse_key_content(conn, key)
    else
      _ -> conn 
    end
  end

  defp maybe_parse_key_content(%{body_params: body_params} = conn, key) do
    with {:ok, content} when is_binary(content) <- Map.fetch(body_params, key),
         {:ok, decoded_content} <- Jason.decode(content) do
      put_in(conn, [Access.key(:body_params), key], decoded_content)
    else
      _ -> conn
    end
  end
end

It works, but I feel that Phoenix should do that automatically.

So, is there some way to tell the Phoenix Controller to properly parse that field for me?

Have you taken a look at the Plug documentation and the built-in parsers?

I did, but I couldn’t make it work for the above case

You want to look at Plug.Parsers.MULTIPART — Plug v1.19.1 to customize how you treat the json file vs the image file. On a multipart request all you get are named files with data. The parser cannot tell that you want json to be further parsed, while the image is meant to be kept as an “uploaded file”.

Thanks, I will take a look at that plug.

But just a question about this part:

The parser cannot tell that you want json to be further parsed

Why is that? I do see that the information that the json type is application/json is sent to the server, shouldn’t the plug parser see that and automatically parse it anyway?

You’re assuming that’s the correct way to handle it because that’s your usecase here. People do upload json files on file inputs and they’re expected to not be treated differently than other files uploaded through the same input field. If you need certain files on the request to be treated differently that’s for you to implement.

2 Likes