0

假设我正在构建一个版本化的 API(例如,让我们使用一个用户对象):

%User{
  id: "b2507407-891b-486e-aaf8-ba262c16d618"
  first_name: "John",
  last_name: "Doe",
  email: "john@doe.com"
}

我最初的想法是为不同版本的 API 使用多个编码器,我将运行不同的 Jason 编码器:

defimpl Jason.EncoderV1, for: __MODULE__ do
  def encode(user, opts) do
    Jason.Encode.map(%{name: "#{user.first_name} #{user.last_name}"}, opts)
  end
end

defimpl Jason.EncoderV2, for: __MODULE__ do
  def encode(user, opts) do
    Jason.Encode.map(%{first_name: first_name, last_name: last_name}, opts)
  end
end

我在 Jason 文档中没有看到任何允许这样做的参考。

4

2 回答 2

2

您应该以某种方式告诉Jason您要使用哪个版本。Jason.Encoder.encode/2有第二个参数。

defimpl Jason.Encoder, for: __MODULE__ do
  def encode(user, opts) do
    {v, opts} = Keyword.pop(opts, :version, :v1)

    v
    |> case do
      :v2 -> %{first_name: first_name, last_name: last_name}
      _ -> %{name: "#{user.first_name} #{user.last_name}"}
    end
    |> Jason.Encode.map(opts)
  end
end

并称其为Jason.encode!(any, version: :v2).

旁注: defimpl期望将协议定义为第一个参数的模块,一个不能传递任何东西,比如那里不Jason.EncoderV2存在。

于 2020-05-13T18:06:22.363 回答
0

您的示例代码提出了一个更多关于 Elixir协议的问题,但要小心:Jason 包定义了自己的协议,不受您的版本控制 - 即您的实现将始终使用defimpl Jason.Encoder.

但是,您可以为不同的结构定义不同的实现,例如

defimpl Jason.Encoder, for: UserV1 do
  def encode(user, opts) do
    Jason.Encode.map(%{name: "#{user.first_name} #{user.last_name}"}, opts)
  end
end

defimpl Jason.Encoder, for: UserV2 do
  def encode(user, opts) do
    Jason.Encode.map(%{first_name: first_name, last_name: last_name}, opts)
  end
end

或者,利用 Aleksei 在他的回答中指出的选项(第二个参数)。

换句话说,您无法更改协议的名称(因为它不受您的控制),但您可以通过提供不同模块的不同名称(您可以控制)来更改功能

这里更简单的解决方案没有协议实现那么迷人:

  1. 您可以简单地为您的不同用例定义 JSON 视图
  2. 您可以编写自定义v1v2函数,将%User{}结构转换为常规映射(每个都需要任何字段),然后依赖 JSON 库(Jason在您的情况下)及其编码协议提供的标准 JSON 编码。
于 2020-05-13T18:30:12.763 回答