Ecto: coerce 0 to nil on integer fields

I’m jamming JSON into a changeset and all is well, except this particular source of info gives me 0 values for integer fields instead of null/nil values. These are non-zero fields and zero will never represent anything other than nil.

I would prefer to be consistent and keep nil as the value in my DB when I don’t get something for these values, since that’s what these 0’s mean. Is there a neat way to do this on the changeset/cast level without me having to map through and coerce these values per key myself?

To be super clear, how do I save “{ name: “String”, not_zero_thing: 0, other_thing: 0 }” as { name: “String”, not_zero_thing: nil, other_thing: nil } without having to manually map the keys?

You can achieve this with a custom Ecto type, something like this should work:

defmodule NonZeroInteger do
  @behaviour Ecto.Type

  @impl true
  def type(), do: :integer

  @impl true
  def cast(0), do: {:ok, nil}
  def cast(other), do: Ecto.Type.cast(:integer, other)

  @impl true
  def dump(0), do: {:ok, nil}
  def dump(other), do: Ecto.Type.dump(:integer, other)

  @impl true
  def load(other), do: Ecto.Type.load(:integer, other)
end

Note, when getting data back from the DB (the load/1 callback) we won’t be able to turn nil back to 0, Ecto always passes nils through on every callback.

2 Likes

What field type are you using in the database? Are you storing all the keys in separate fields?

Sorry yeah, each field is its own column, and each one is typed as an integer.