如何在Elixir中正确扩展现有协议?
例如:Poison.encode!(:null)
返回“null”。
以Poison.Encoder
协议为例。我想在Atom
的议定书中增加一个。但是,如果不重新定义本议定书中的所有内容,我不知道如何做到这一点。
defimpl Poison.Encoder, for: Atom do
alias Poison.Encoder
def encode(nil, _), do: "null"
def encode(true, _), do: "true"
def encode(false, _), do: "false"
# /------------------------------------------------------
# V My addition - What is the correct way of doing this?
def encode(:null, _), do: "null"
def encode(atom, options) do
Encoder.BitString.encode(Atom.to_string(atom), options)
end
end
嗨,我遇到了JSON编码的特殊问题,这引出了我的问题。
我希望所有:null
Atoms都在JSON中编码为null
,而不是默认的"null"
(作为字符串)。我使用的JSON库是Poison
。
现在,上面的工作,但它吐出警告,如:
warning: redefining module Poison.Encoder.Atom (current version loaded from _build/dev/lib/poison/ebin/Elixir.Poison.Encoder.Atom.beam)
lib/atom_encoder.ex:19
重新实现内置类型的协议不是一个好主意。您正在使用的其他软件包可能依赖于实现的原始行为。我将使用一个函数,用:null
递归替换所有nil
值,然后将其传递给Poison.encode
。您可以创建一个包装函数来执行此转换,然后根据需要调用Poison.encode
。这是一个基本的实现:
defmodule A do
def null_to_nil(:null), do: nil
def null_to_nil(list) when is_list(list) do
Enum.map(list, &null_to_nil/1)
end
def null_to_nil(map) when is_map(map) do
Enum.map(map, fn {k, v} -> {k, null_to_nil(v)} end) |> Map.new
end
def null_to_nil(term), do: term
end
IO.inspect A.null_to_nil([1, 2, :null, %{a: :null, b: %{b: [:null, :null]}}])
输出:
[1, 2, nil, %{a: nil, b: %{b: [nil, nil]}}]
无法在Elixir中扩展现有协议。可能的解决方法是:
1.考虑使用像%Null{}
而不是:null
原子这样的东西,并为这个特殊结构实现Poison.Encoder
(我无法使用@derive
用于此目的):
defmodule Null do
# @derive [Poison.Encoder] # that won’t work
defstruct [json: "null"]
def encode(%Null{json: json}), do: json
end
defimpl Poison.Encoder, for: Null do
def encode(%Null{} = null), do: Null.encode(null)
end
2.在加载你的模块之前,有人可能会使用Erlang的Poison.Encoder
和:code.delete/1
(不推荐)从你的来源强制重装:code.purge/1
,并[可选]加强与Protocol.consolidate/2
的整合:
:code.ensure_loaded(Poison.Encoder)
Protocol.assert_protocol!(Poison.Encoder)
:code.ensure_loaded(Poison.Encoder.Atom)
:code.delete(Poison.Encoder.Atom)
:code.purge(Poison.Encoder.Atom)
# your implementation
defimpl Poison.Encoder, for: Atom do
...
end