Background
I am trying to encode a structure into json format using the Jason library. However, this is not working as expected.
Code
Let's assume I have this struct:
defmodule Test do
defstruct [:foo, :bar, :baz]
end
And that when using Jason.enconde(%Test{foo: 1, bar: 2, baz:3 })
I want this json to be created:
%{"foo" => 1, "banana" => 5}
Error
It is my understanding that to achieve this I need to implement the Jason.Enconder
protocol in my struct:
https://hexdocs.pm/jason/Jason.Encoder.html
defmodule Test do
defstruct [:foo, :bar, :baz]
defimpl Jason.Encoder do
@impl Jason.Encoder
def encode(value, opts) do
Jason.Encode.map(%{foo: Map.get(value, :foo), banana: Map.get(value, :bar, 0) + Map.get(value, :baz, 0)}, opts)
end
end
end
However, this will not work:
Jason.encode(%Test{foo: 1, bar: 2, baz: 3})
{:error,
%Protocol.UndefinedError{
description: "Jason.Encoder protocol must always be explicitly implemented.\n\nIf you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:\n\n @derive {Jason.Encoder, only: [....]}\n defstruct ...\n\nIt is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:\n\n @derive Jason.Encoder\n defstruct ...\n\nFinally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:\n\n Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])\n Protocol.derive(Jason.Encoder, NameOfTheStruct)\n",
protocol: Jason.Encoder,
value: %Test{bar: 2, baz: 3, foo: 1}
}}
From what I understand, it looks like I can only select/exclude keys to serialize, I cannot transform/add new keys.
Since I own the structure in question, using Protocol.derive
is not necessary.
However I fail to understand how I can leverage the Jason.Encoder
protocol to achieve what I want.
Questions
- Is my objective possible using the Jason library, or is this a limitation?
- Am I miss understanding the documentation and doing something incorrect?
My guess is, this is due to writing the protocol inside a test file. Protocol consolidation happens before the test file executes, so the protocol never becomes part of the compiled codebase.
To elaborate with an example...
I did the following in a Phoenix app
Running this test fule, results in the "encodes Foo" passing, but "encodes Bar" fails with a warning
followed by an error in the test
This is because of protocol consolidation happening, causing the Bar protocol to not be compiled.
You can turn off protocol consolidation in the test environment, by adding the following to
mix.exs
If you do that, the protocol will compile and both tests will pass.
However, the solution is probably to just not write the struct/protocol directly in the test file.