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)
end
defp sanitize_map(map) do
Map.drop(map, [:__meta__, :__struct__])
end
end
This relates to this github issue.
Written by Estevão Mascarenhas
Related protips
6 Responses
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
@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.
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: "test.com", email: "est.tester@test.com"} #, lastused: Ecto.DateTime.utc, uniquecode: Ecto.UUID.generate(), isactive: :true }
@invalid_attrs %{domain: "", email: ""}
setup do
conn = conn()
{:ok, conn: conn}
end
test "/api call gets organization", %{conn: conn} do
org =
%Organization{domain: "test.com", email: "test.tester@test.com"}
|> 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
end
end
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. :/
Hi @rheckart i have found a way and it works for me. Try to change:
elixir
org =
%Organization{domain: "test.com", email: "test.tester@test.com"}
|> 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
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/tarefaapicontroller.ex:1: Mechanize.TarefaApiController.action/2
(mechanize) web/controllers/tarefaapicontroller.ex:1: Mechanize.TarefaApiController.phoenixcontrollerpipeline/2
(mechanize) lib/phoenix/router.ex:261: Mechanize.Router.dispatch/2
(mechanize) web/router.ex:1: Mechanize.Router.docall/2
(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/rendererrors.ex:34: Mechanize.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) src/cowboyprotocol.erl:442: :cowboyprotocol.execute/4