Skip to content

Commit

Permalink
Add encode_to_iodata support in Protobuf.JSON (#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
v0idpwn authored Dec 29, 2024
1 parent b1cfa5f commit 9ff73ed
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 4 deletions.
40 changes: 36 additions & 4 deletions lib/protobuf/json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ defmodule Protobuf.JSON do
"""
@spec encode!(struct, [encode_opt]) :: String.t() | no_return
def encode!(struct, opts \\ []) do
case encode(struct, opts) do
{:ok, json} -> json
case encode_to_iodata(struct, opts) do
{:ok, iodata} -> IO.iodata_to_binary(iodata)
{:error, error} -> raise error
end
end
Expand Down Expand Up @@ -169,9 +169,41 @@ defmodule Protobuf.JSON do
"""
@spec encode(struct, [encode_opt]) ::
{:ok, String.t()} | {:error, EncodeError.t() | Exception.t()}
def encode(%_{} = struct, opts \\ []) when is_list(opts) do
def encode(struct, opts \\ []) when is_list(opts) do
case encode_to_iodata(struct, opts) do
{:ok, iodata} -> {:ok, IO.iodata_to_binary(iodata)}
{:error, error} -> {:error, error}
end
end

@doc """
Similar to `encode!/2`, but returns iodata
See `encode_to_iodata/2` for more information about when this function should
be preferred over `encode!/2`.
"""
@spec encode_to_iodata!(struct, [encode_opt]) :: iodata()
def encode_to_iodata!(struct, opts \\ []) when is_list(opts) do
case encode_to_iodata(struct, opts) do
{:ok, iodata} -> iodata
{:error, error} -> raise error
end
end

@doc """
Similar to `encode/2`, but returns iodata
This function should be preferred to encode/2, if the generated JSON will be
handed over to one of the IO functions or sent over the socket. The Erlang
runtime is able to leverage vectorised writes and avoid allocating a continuous
buffer for the whole resulting string, lowering memory use and increasing
performance.
"""
@spec encode_to_iodata(struct, [encode_opt]) ::
{:ok, iodata()} | {:error, EncodeError.t() | Exception.t()}
def encode_to_iodata(%_{} = struct, opts \\ []) when is_list(opts) do
if jason = load_jason() do
with {:ok, map} <- to_encodable(struct, opts), do: jason.encode(map)
with {:ok, map} <- to_encodable(struct, opts), do: jason.encode_to_iodata(map)
else
{:error, EncodeError.new(:no_json_lib)}
end
Expand Down
6 changes: 6 additions & 0 deletions test/protobuf/json_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ defmodule Protobuf.JSONTest do
assert Protobuf.JSON.encode!(%TestMsg.Foo2{b: 10}) == ~S|{"b":"10"}|
end

test "encode_to_iodata variants encode to iodata" do
assert iodata = Protobuf.JSON.encode_to_iodata!(%TestMsg.Foo2{b: 10})
assert {:ok, ^iodata} = Protobuf.JSON.encode_to_iodata(%TestMsg.Foo2{b: 10})
assert IO.iodata_to_binary(iodata) == ~S|{"b":"10"}|
end

test "encoding string field with invalid UTF-8 data" do
message = %Scalars{string: " \xff "}
assert {:error, %Jason.EncodeError{}} = Protobuf.JSON.encode(message)
Expand Down

0 comments on commit 9ff73ed

Please sign in to comment.