When building JSON APIs you often find yourself in a situation when you have part of the JSON already encoded and want to embed it in a bigger structure. A common solution is to decode the encoded part and embed the outer structure just to encode it back again. It’s obvious how that back-and-forth decoding and encoding is wasteful. Fortunately with poison we have a much better alternative.

Poison encoding is based on protocols, this means we can define how a particular Elixir struct should be encoded - and it doesn’t have to be a structural encoding - it can be something completely unrelated to the internal representation. We’re going to leverage that here, by creating a JSONFragment struct and implementing the Poison.Encoder for it to return the encoded JSON “fragment”.

defmodule JSONFragment do
  defstruct iodata: []

  def new(iodata) when is_binary(iodata) or is_list(iodata) do
    %__MODULE__{iodata: iodata}
  end
end

defimpl Poison.Encoder, for: JSONFragment do
  def encode(%{iodata: iodata}, _options) do
    iodata
  end
end

And that’s all. Simple, wasn’t it? That’s the power of protocol-based encoding.

How do we use it? Let’s see couple examples how we could use it in Phoenix. We can use it for a part of response in a Phoenix view:

def render("show.json", %{user: user, json: json}) do
  %{id: user.id,
    name: user.name,
    data: JSONFragment.new(json)}
end

Or to respond directly in a Phoenix channel:

def handle_in("load_data", _payload, socket) do
  data = load_json_string()
  {:reply, {:ok, JSONFragment.new(data)}, socket}
end

Or in any other place that’s going to encode our data with poison. One thing you may have noticed, is that I accept a binary or a list instead of a string, and call the value “iodata” - it’s an optimisation for constant-time string concatenation common in Elixir and Erlang libraries. You can read more about it in the excellent article by Nathan Long.