Protocol Jason.Encoder not implemented for {:ok, #Money<:USD, 20000>} of type Tuple, Jason.Encoder protocol must always be explicitly implemented

Hello, I have a very newbie question if anyone knows.

I’m using this libraries

{:ex_money, "~> 5.5.1"},
{:ex_money_sql, "~> 1.4.4"}

to deal with Money. First time I work with Money types because I was doing it using only decimals.

I configured this libraries well, I know because I can seed data with no problem using this:

Repo.insert!(%InnovationProject{
  budget: Money.new(:USD, 7800),
  term: 2
})

and receiving no errors at that time. And when doing GET requests the format is a Money type in the result.
So, with that out of the way, I have the following Ecto.Schema on Phoenix Framework:

schema "innovation_projects" do
    field :budget, Money.Ecto.Composite.Type
    field :term, :decimal
    field :anual_cost, Money.Ecto.Composite.Type, virtual: true

    timestamps()
  end

My changeset:

def changeset(innovation_project, attrs) do
    innovation_project
    |> cast(attrs, [
      :budget,
      :term
    ])
    |> validate_required([
      :budget,
      :term
    ])
    |> calculate_anual_cost()
  end

And calculate_anual_cost function looks like this:

 defp calculate_anual_cost(changeset) do
    budget = get_change(changeset, :budget)
    term = get_change(changeset, :term)

    changeset
    |> put_change(:anual_cost, budget |> Money.div(term))
  end

Now, I think that there’s no problem here. But my error comes when I want to show anual_cost on my views

So, my innovation_project_view.ex looks like this:

def render("innovation_project.json", %{innovation_project: innovation_project}) do
    %{
      budget: innovation_project.budget,
      term: innovation_project.term,
      anual_cost: innovation_project.anual_cost
    }
  end

Eyes on anual_cost: innovation_project.anual_cost because that’s the line giving me this issue:

protocol Jason.Encoder not implemented for {:ok, #Money<:USD, 20000>} of type Tuple, Jason.Encoder protocol must always be explicitly implemented. This protocol is implemented for the following type(s): Money, Cldr.LanguageTag, Ecto.Association.NotLoaded, Ecto.Schema.Metadata, BitString, List, Integer, Any, Date, Map, NaiveDateTime, Atom, Jason.Fragment, DateTime, Decimal, Time, Float

So, my view can show budget, which is a Money type but the generated virtual field anual_cost can’t be shown. How can I show my anual_cost?

One more thing:

iex> Money.div Money.new(:USD, 200), 2
    {:ok, Money.new(:USD, 100)} 

Thanks in advance

1 Like

The Money.div function returns a tuple of {:ok, value} or {:error, reason}. So where you are using the function, you need to pick the value out of the tuple. Jason won’t encode tuples as JSON by default, so that’s what the error comes from.

So in your changeset function you should be doing maybe something like:

with {:ok, cost} <- Money.div(budget, term) do
  put_change(changeset, :anual_cost, cost)
else
  {:error,  reason} ->
    put_error(changeset, ... something something)
end

Btw annual is written with two n’s. :slight_smile:

5 Likes

HI, thank you very much. You are so right. I told you I was a newbie :slight_smile:

Also thanks for the typo clarification in annual :ok_hand: It made a lot of sense in Spanish to me :sweat_smile:

2 Likes