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