如何使用Jason对结构进行自定义编码



背景

我正在尝试使用Jason库将一个结构编码为json格式。然而,这并没有如预期的那样奏效。

代码

假设我有这样的结构:

defmodule Test do
defstruct [:foo, :bar, :baz]
end

当使用Jason.enconde(%Test{foo: 1, bar: 2, baz:3 })时,我希望创建这个json:

%{"foo" => 1, "banana" => 5}

错误

我的理解是,为了实现这一点,我需要在我的结构中实现Jason.Enconder协议: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

然而,这将不起作用:

Jason.encode(%Test{foo: 1, bar: 2, baz: 3})
{:error,
%Protocol.UndefinedError{
description: "Jason.Encoder protocol must always be explicitly implemented.nnIf you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:nn    @derive {Jason.Encoder, only: [....]}n    defstruct ...nnIt is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:nn    @derive Jason.Encodern    defstruct ...nnFinally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:nn    Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])n    Protocol.derive(Jason.Encoder, NameOfTheStruct)n",
protocol: Jason.Encoder,
value: %Test{bar: 2, baz: 3, foo: 1}
}}

据我所知,我似乎只能选择/排除要序列化的键,而不能转换/添加新键。由于我拥有所讨论的结构,所以不需要使用Protocol.derive

然而,我不明白如何利用Jason.Encoder协议来实现我想要的。

问题

  1. 我的目标是使用Jason库,还是这是一个限制
  2. 我是不是错过了理解文档和做一些不正确的事情

我的猜测是,这是由于在测试文件中写入了协议。协议合并发生在测试文件执行之前,因此协议永远不会成为编译的代码库的一部分。

举例说明。。。

我在Phoenix应用中做了以下操作

  • 到lib文件夹中,我添加了foo.ex
defmodule Foo do
defstruct [:a, :b]
defimpl Jason.Encoder do
def encode(%Foo{a: a, b: b}, opts) do
Jason.Encode.map(%{"a" => a, "b" => b}, opts)
end
end
end
  • 在测试文件夹中,我添加了foo_test.exs
defmodule FooTest do
use ExUnit.Case
defmodule Bar do
defstruct [:c, :d]
defimpl Jason.Encoder do
def encode(%Bar{c: c, d: d}, opts) do
Jason.Encode.map(%{"c" => c, "d" => d}, opts)
end
end
end
test "encodes Foo" do
%Foo{a: 1, b: 2} |> Jason.encode!() |> IO.inspect()
end
test "encodes Bar" do
%Bar{c: 5, d: 6} |> Jason.encode!()
end
end

运行该测试fule;编码Foo";通过,但";编码条";出现故障并发出警告

warning: the Jason.Encoder protocol has already been consolidated, an implementation for FooTest.Bar has no effect. If you want to implement protocols after compilation or during tests, check the "Consolidation" section in the Protocol module documentation

随后在测试中出现错误

** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for %FooTest.Bar{c: 5, d: 6} of type FooTest.Bar (a struct), Jason.Encoder protocol must always be explicitly implemented.

这是因为发生了协议合并,导致Bar协议无法编译。

您可以通过在mix.exs中添加以下内容来关闭测试环境中的协议整合

def project do
# ...
consolidate_protocols: Mix.env() != :test,                   
#...
end

如果你这样做,协议将编译,两个测试都将通过

然而,解决方案可能只是不直接在测试文件中写入结构/协议。

最新更新