Last Updated: December 26, 2017
· estevaoam

Fix encoding issue with Ecto and Poison

In the newest version of Ecto 0.15.0 I had the following error when trying to encode a struct model:

%User{name: "test"} 

But this throws an error:

unable to encode value: {nil, "users"}

Why? Because it contains the private data __meta__ inside the struct, with a tuple as value, which is not parsed correctly by Poison.

The solution was to implement the following Poison.Encoder clause case:

defimpl Poison.Encoder, for: Any do
  def encode(%{__struct__: _} = struct, options) do
    map = struct
          |> Map.from_struct
          |> sanitize_map
    Poison.Encoder.Map.encode(map, options)

  defp sanitize_map(map) do
    Map.drop(map, [:__meta__, :__struct__])

This relates to this github issue.

6 Responses
Add your response

Sorry, dumb question.. where and what name do you give when saving this file? (n00b)

I've placed it in lib/poison_encoder.ex but get

no function clause matching in Poison.Encoder.Any.encode/2

thanks in advance

over 1 year ago ·

@rosscdh thanks for your comment!

I put this in the file lib/myapp/encoder.ex file, note that I was using an Phoenix application, it does load files automatically.

If you're using it in a plain Elixir application, try to import the Poison.Encoder to your file and see if it works. Don't forget that you'll need to require this file in order to load this implementation for the encoder.

Let me know if you had success.

over 1 year ago ·

I'm also an Elixir newb. I'm trying to use this inside of a test case in Phoenix, but I'm getting the same error as @rosscdh. It seems like no matter where I put the require and/or import, the error is the same. My code in the test is:

defmodule Jiranomohub.OrganizationApiControllerTest do
use Jiranomohub.ConnCase

alias Poison.Encoder
alias Jiranomohub.Organization

require Poison.Encoder
import Poison.Encoder

@validattrs %{domain: "", email: ""} #, lastused: Ecto.DateTime.utc, uniquecode: Ecto.UUID.generate(), isactive: :true }
@invalid_attrs %{domain: "", email: ""}

setup do
conn = conn()
{:ok, conn: conn}

test "/api call gets organization", %{conn: conn} do

org = 
  %Organization{domain: "", email: ""}
  |> Repo.insert
  |> List.wrap

org_as_json = Poison.encode!(org)

conn = get conn, organization_api_path(conn, :show, org_as_json)
# response = conn(:get, "/api/organizations") |> send_request

assert html_response(conn, 200) =~ org_as_json
# assert response.status == 200
# assert response.resp_body == org_as_json


over 1 year ago ·

Hi @rheckart what is the version are you using at?

When I wrote this article a lot of libraries wasn't on the 1.x version yet, maybe something changed. :/

over 1 year ago ·

Hi @rheckart i have found a way and it works for me. Try to change:
elixir org = %Organization{domain: "", email: ""} |> Repo.insert org_as_json = Repo.all(Organization) |> Poison.encode!(org)
then you will not need to serialize the list of organization objects. You can just use
elixir @derive {Poison.Encoder, only: [:foo, :bar, :baz]} schema "your schema" do field :foo field :bar field :bak end
with Poison 1.5

over 1 year ago ·

Thanks! With your help I am getting the json response but I now get this error:

Any idea how to fix it? Thanks

[info] Sent 200 in 773µs
[debug] SELECT t0."id", t0."titulo", t0."url", t0."insertedat", t0."updatedat" FROM "tarefas" AS t0 [] OK query=0.3ms
[error] #PID<0.428.0> running Mechanize.Endpoint terminated
Server: localhost:4000 (http)
Request: GET /api/tarefas
** (exit) an exception was raised:
** (Plug.Conn.AlreadySentError) the response was already sent
(plug) lib/plug/conn.ex:458: Plug.Conn.resp/3
(plug) lib/plug/conn.ex:445: Plug.Conn.sendresp/3
(mechanize) web/controllers/tarefa
apicontroller.ex:1: Mechanize.TarefaApiController.action/2
(mechanize) web/controllers/tarefa
apicontroller.ex:1: Mechanize.TarefaApiController.phoenixcontrollerpipeline/2
(mechanize) lib/phoenix/router.ex:261: Mechanize.Router.dispatch/2
(mechanize) web/router.ex:1:
(mechanize) lib/mechanize/endpoint.ex:1: Mechanize.Endpoint.phoenixpipeline/1
(mechanize) lib/plug/debugger.ex:93: Mechanize.Endpoint."call (overridable 3)"/2
(mechanize) lib/phoenix/endpoint/render
(plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) src/cowboyprotocol.erl:442: :cowboyprotocol.execute/4

over 1 year ago ·