使用 Jason 在 Phoenix 中编码 ObjectId

encoding ObjectId in Phoenix using Jason

我在 Phoenix 有一个频道,我从中获取对象列表 MongoDB:

cursor = Mongo.find(:mongo, "ledgers", %{})
list = Enum.to_list(cursor)

我试着发送这个:

broadcast(socket, "load_ledgers_do", %{payload: list})

这个returns错误:

[error] GenServer #PID<0.3147.0> terminating
** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for #BSON.ObjectId<603e521606fc6d39bd3e641d> of type BSON.ObjectId (a struct), Jason.Encoder protocol must always be explicitly implemented.

If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:

    @derive {Jason.Encoder, only: [....]}
    defstruct ...

It is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:

    @derive Jason.Encoder
    defstruct ...

Finally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:

    Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])
    Protocol.derive(Jason.Encoder, NameOfTheStruct)

还有一些。

现在回答我的问题,我该如何最好地处理这个错误?

我试过使用 defimpl:

defimpl Jason.Encoder, for: BSON.ObjectId do
  def encode(val, _opts \ []) do
    BSON.ObjectId.encode!(val)
    |> Jason.encode()
  end
end

但这returns一个错误。确保正确编码 ObjectId 的最佳方法是什么?

编辑错误:

[error] Ranch listener ServerWeb.Endpoint.HTTP had connection process started with :cowboy_clear:start_link/4 at #PID<0.3641.0> exit with reason: {:badarg, [{:erlang, :iolist_size, [[91, "null", 44, "null", 44, [34, [[] | "ledgers"], 34], 44, [34, [[] | "load_ledgers_do"], 34], 44, ["{\"", [[] | "payload"], "\":", [91, ["{\"", [[] | "_id"], "\":", {:ok, "\"603e521606fc6d39bd3e641d\""}, ",\"", [[] | "collection"], "\":", [34, [[] | "ledger_603e521606fc6d39bd3e641d"], 34], ",\"", [[] | "columns"], "\":", "[]", ",\"", [[] | "name"], "\":", [34, [[] | "Ledger1"], 34], ",\"", [[] | "x"], "\":", "427", ",\"", [[] | "y"], "\":", "283", 125], 93], 125], 93]], []}, {:cow_ws, :payload_length, 1, [file: '/home/lars/bdrp/server/deps/cowlib/src/cow_ws.erl', line: 725]}, {:cow_ws, :frame, 2, [file: '/home/lars/bdrp/server/deps/cowlib/src/cow_ws.erl', line: 666]}, {:cowboy_websocket, :websocket_send, 2, [file: '/home/lars/bdrp/server/deps/cowboy/src/cowboy_websocket.erl', line: 626]}, {:cowboy_websocket, :handler_call, 6, [file: '/home/lars/bdrp/server/deps/cowboy/src/cowboy_websocket.erl', line: 542]}, {:cowboy_http, :loop, 1, [file: '/home/lars/bdrp/server/deps/cowboy/src/cowboy_http.erl', line: 254]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}

[error] GenServer #PID<0.3644.0> terminating
** (ArgumentError) argument error
:erlang.iolist_size([91, "null", 44, "null", 44, [34, [[] | "ledgers"], 34], 44, [34, [[] | "load_ledgers_do"], 34], 44, ["{\"", [[] | "payload"], "\":", [91, ["{\"", [[] | "_id"], "\":", {:ok, "\"603e521606fc6d39bd3e641d\""}, ",\"", [[] | "collection"], "\":", [34, [[] | "ledger_603e521606fc6d39bd3e641d"], 34], ",\"", [[] | "columns"], "\":", "[]", ",\"", [[] | "name"], "\":", [34, [[] | "Ledger1"], 34], ",\"", [[] | "x"], "\":", "427", ",\"", [[] | "y"], "\":", "283", 125], 93], 125], 93])
(cowlib 2.9.1) /home/lars/bdrp/server/deps/cowlib/src/cow_ws.erl:725: :cow_ws.payload_length/1
(cowlib 2.9.1) /home/lars/bdrp/server/deps/cowlib/src/cow_ws.erl:666: :cow_ws.frame/2
(cowboy 2.8.0) /home/lars/bdrp/server/deps/cowboy/src/cowboy_websocket.erl:626: :cowboy_websocket.websocket_send/2
(cowboy 2.8.0) /home/lars/bdrp/server/deps/cowboy/src/cowboy_websocket.erl:542: :cowboy_websocket.handler_call/6
(cowboy 2.8.0) /home/lars/bdrp/server/deps/cowboy/src/cowboy_http.erl:254: :cowboy_http.loop/1
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: {:DOWN, #Reference<0.3631150579.3565682692.115779>, :process, #PID<0.3641.0>, {:badarg, [{:erlang, :iolist_size, [[91, "null", 44, "null", 44, [34, [[] | "ledgers"], 34], 44, [34, [[] | "load_ledgers_do"], 34], 44, ["{\"", [[] | "payload"], "\":", [91, ["{\"", [[] | "_id"], "\":", {:ok, "\"603e521606fc6d39bd3e641d\""}, ",\"", [[] | "collection"], "\":", [34, [[] | "ledger_603e521606fc6d39bd3e641d"], 34], ",\"", [[] | "columns"], "\":", "[]", ",\"", [[] | "name"], "\":", [34, [[] | "Ledger1"], 34], ",\"", [[] | "x"], "\":", "427", ",\"", [...], ...], 93], 125], 93]], []}, {:cow_ws, :payload_length, 1, [file: '/home/lars/bdrp/server/deps/cowlib/src/cow_ws.erl', line: 725]}, {:cow_ws, :frame, 2, [file: '/home/lars/bdrp/server/deps/cowlib/src/cow_ws.erl', line: 666]}, {:cowboy_websocket, :websocket_send, 2, [file: '/home/lars/bdrp/server/deps/cowboy/src/cowboy_websocket.erl', line: 626]}, {:cowboy_websocket, :handler_call, 6, [file: '/home/lars/bdrp/server/deps/cowboy/src/cowboy_websocket.erl', line: 542]}, {:cowboy_http, :loop, 1, [file: '/home/lars/bdrp/server/deps/cowboy/src/cowboy_http.erl', line: 254]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}}
State: %Phoenix.Socket{assigns: %{}, channel: ServerWeb.LedgersChannel, channel_pid: #PID<0.3644.0>, endpoint: ServerWeb.Endpoint, handler: ServerWeb.UserSocket, id: nil, join_ref: "141", joined: true, private: %{log_handle_in: :debug, log_join: :info}, pubsub_server: Server.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "ledgers", transport: :websocket, transport_pid: #PID<0.3641.0>}

此外,我收到了 defimpl 中编码函数的 linting 警告:

 Type mismatch for @callback encode/2 in Jason.Encoder behaviour.

 Expected type:

   binary()
   | maybe_improper_list(
       binary() | maybe_improper_list(any(), binary() | []) | byte(),
       binary() | []
     )


 Actual type:

   {:error,
    %{
      :__exception__ => _,
      :__struct__ => Jason.EncodeError | Protocol.UndefinedError,
      atom() => _
    }}
   | {:ok, binary()}

我至今没能摆脱它。

这有点棘手,因为二进制 ID 的编码并不总是很好。

您使用的是哪个 Mongo 库?如果您正在使用 mongodb,您可以使用它的 BSON.encode/1 来帮助转换二进制文件,您可以尝试这样的操作:

defimpl Jason.Encoder, for: BSON.ObjectId do
  def encode(val, _opts \ []) do
    val
    |> BSON.encode()
    |> Base.encode16(case: :lower)
  end
end
iex> o = %BSON.ObjectId{value: <<94, 48, 99, 46, 116, 55, 7, 153, 84, 246, 18, 54>>}
#BSON.ObjectId<5e30632e7437079954f61236>
iex> Jason.encode(o)
{:ok, "5e30632e7437079954f61236"}

开始使用了。这似乎是一个正确的解决方案:

defimpl Jason.Encoder, for: BSON.ObjectId do
  def encode(val, _opts \ []) do
    BSON.ObjectId.encode!(val)
    |> Jason.encode!()
  end
end

少了一个感叹号。