lib/encoder.ex

defprotocol Jsonrs.Encoder do
  @moduledoc """
  Protocol controlling how a value is encoded to JSON.

  By default, structs are encoded as maps without the `:__struct__` key. If this
  is sufficient for your use, you don't need to implement this protocol.

  When implementing an encoding function, the goal is to turn your value into
  something already capable of being encoded.

  ## Example

  An implementation of this protocol for `Decimal` might look something like

      defimpl Jsonrs.Encoder, for: Decimal do
        def encode(value) do
          Decimal.to_string(value)
        end
      end

  Which will cause the Decimal to be encoded as its string representation

  """

  @fallback_to_any true

  @doc """
  Converts `value` to a JSON-encodable type.
  """
  def encode(value)
end

defimpl Jsonrs.Encoder, for: Map do
  def encode(map), do: :maps.map(fn _, v -> Jsonrs.Encoder.encode(v) end, map)
end

defimpl Jsonrs.Encoder, for: List do
  def encode(list), do: Enum.map(list, &Jsonrs.Encoder.encode/1)
end

defimpl Jsonrs.Encoder, for: Tuple do
  def encode(tuple) do
    tuple |> Tuple.to_list() |> Jsonrs.Encoder.encode()
  end
end

defimpl Jsonrs.Encoder, for: [Date, Time, NaiveDateTime, DateTime] do
  def encode(d), do: d |> @for.to_iso8601()
end

defimpl Jsonrs.Encoder, for: URI do
  def encode(uri), do: URI.to_string(uri)
end

defimpl Jsonrs.Encoder, for: Any do
  def encode(s) when is_struct(s), do: s |> Map.from_struct() |> Jsonrs.Encoder.encode()
  def encode(any), do: any
end