Custom Jason.Encoder implementation to render NaiveDateTime as DateTime

I’m somewhat new to Elixir. I have a Phoenix project and I need my endpoints to always return DateTime, even when NaiveDateTime fields are passed to render functions on views. I tried to do a custom implementation of Jason.Encoder, but it is not working.

Original implementation (/deps/jason/lib/encoder.ex):

defimpl Jason.Encoder, for: [Date, Time, NaiveDateTime, DateTime] do
  def encode(value, _opts) do
    [?\", @for.to_iso8601(value), ?\"]
  end
end

My implementation (my_project/lib/my_project/custom_encoder.ex)

defimpl Jason.Encoder, for: NaiveDateTime do
  def encode(value, _opts) do
    value |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601()
  end
end

However, this does nothing. Even if I change the original lib file implementation and recompile, I still get naive datetimes on my endpoints. Could anyone shed some light on how could I achieve this?

Many thanks in advance!

Unfortunately an overriding implementation will not work because Elixir does not support overriding protocols. Here is a similar case to yours.

You would need to create a wrapper struct and then create an Jason.Encoder implementation for it. For example:

defmodule MyApp.TzDateTime do
  @type t :: %__MODULE__{value: DateTime.t()}

  @enforce_keys :value
  defstruct value: nil

  @spec new(DateTime.t() | NaiveDateTime.t()) :: t()
  def new(%DateTime{} = value) do
    %__MODULE__{value: value}
  end

  def new(%NaiveDateTime{} = value) do
    %__MODULE__{value: DateTime.from_naive!(value, "Etc/UTC")}
  end

  defimpl Jason.Encoder do
    def encode(%MyApp.TzDateTime{value: value}, _opts), do: DateTime.to_iso8601(value)
  end
end

And on your Phoenix view that renders the JSON:

defmodule MyApp.MyStuffView do
  use MyApp, :view

  def render("index.json", %{mystuffs: mystuffs}) do
    %{data: render_many(mystuffs, MyApp.MyStuffView, "mystuff.json")}
  end

  def render("show.json", %{mystuff: mystuff}) do
    %{data: render_one(mystuff, MyApp.MyStuffView, "mystuff.json")}
  end

  def render("mystuff.json", %{mystuff: mystuff}) do
    %{date: MyApp.TzDateTime.new(Mystuff.my_date_field)}
  end
end

1 Like