Poison
Poison is a new JSON library for Elixir focusing on wicked-fast speed without sacrificing simplicity, completeness, or correctness.
Poison takes several approaches to be the fastest JSON library for Elixir.
Poison uses extensive sub binary matching, a hand-rolled parser using several techniques that are known to benefit BeamAsm for JIT compilation, IO list encoding and single-pass decoding.
Poison benchmarks sometimes puts Poison's performance close to jiffy
and
usually faster than other Erlang/Elixir libraries.
Poison fully conforms to RFC 7159, ECMA 404, and fully passes the JSONTestSuite.
Installation
First, add Poison to your mix.exs
dependencies:
def deps do
[{:poison, "~> 5.0"}]
end
Then, update your dependencies:
$ mix deps.get
Usage
Poison.encode!(%{"age" => 27, "name" => "Devin Torres"})
#=> "{\"name\":\"Devin Torres\",\"age\":27}"
Poison.decode!(~s({"name": "Devin Torres", "age": 27}))
#=> %{"age" => 27, "name" => "Devin Torres"}
defmodule Person do
@derive [Poison.Encoder]
defstruct [:name, :age]
end
Poison.encode!(%Person{name: "Devin Torres", age: 27})
#=> "{\"name\":\"Devin Torres\",\"age\":27}"
Poison.decode!(~s({"name": "Devin Torres", "age": 27}), as: %Person{})
#=> %Person{name: "Devin Torres", age: 27}
Poison.decode!(~s({"people": [{"name": "Devin Torres", "age": 27}]}),
as: %{"people" => [%Person{}]})
#=> %{"people" => [%Person{age: 27, name: "Devin Torres"}]}
Every component of Poison (encoder, decoder, and parser) are all usable on
their own without buying into other functionality. For example, if you were
interested purely in the speed of parsing JSON without a decoding step, you
could simply call Poison.Parser.parse
.
Parser
iex> Poison.Parser.parse!(~s({"name": "Devin Torres", "age": 27}), %{})
%{"name" => "Devin Torres", "age" => 27}
iex> Poison.Parser.parse!(~s({"name": "Devin Torres", "age": 27}), %{keys: :atoms!})
%{name: "Devin Torres", age: 27}
Note that keys: :atoms!
reuses existing atoms, i.e. if :name
was not
allocated before the call, you will encounter an argument error
message.
You can use the keys: :atoms
variant to make sure all atoms are created as
needed. However, unless you absolutely know what you're doing, do not do
it. Atoms are not garbage-collected, see
Erlang Efficiency Guide
for more info:
Atoms are not garbage-collected. Once an atom is created, it will never be removed. The emulator will terminate if the limit for the number of atoms (1048576 by default) is reached.
Encoder
iex> Poison.Encoder.encode([1, 2, 3], %{}) |> IO.iodata_to_binary
"[1,2,3]"
Anything implementing the Encoder protocol is expected to return an IO list to be embedded within any other Encoder's implementation and passable to any IO subsystem without conversion.
defimpl Poison.Encoder, for: Person do
def encode(%{name: name, age: age}, options) do
Poison.Encoder.BitString.encode("#{name} (#{age})", options)
end
end
For maximum performance, make sure you @derive [Poison.Encoder]
for any
struct you plan on encoding.
Encoding only some attributes
When deriving structs for encoding, it is possible to select or exclude
specific attributes. This is achieved by deriving Poison.Encoder
with the
:only
or :except
options set:
defmodule PersonOnlyName do
@derive {Poison.Encoder, only: [:name]}
defstruct [:name, :age]
end
defmodule PersonWithoutName do
@derive {Poison.Encoder, except: [:name]}
defstruct [:name, :age]
end
In case both :only
and :except
keys are defined, the :except
option is
ignored.
Key Validation
According to RFC 7159 keys in a JSON object should be unique. This is enforced and resolved in different ways in other libraries. In the Ruby JSON library for example, the output generated from encoding a hash with a duplicate key (say one is a string, the other an atom) will include both keys. When parsing JSON of this type, Chromium will override all previous values with the final one.
Poison will generate JSON with duplicate keys if you attempt to encode a map
with atom and string keys whose encoded names would clash. If you'd like to
ensure that your generated JSON doesn't have this issue, you can pass the
strict_keys: true
option when encoding. This will force the encoding to fail.
Note: Validating keys can cause a small performance hit.
iex> Poison.encode!(%{:foo => "foo1", "foo" => "foo2"}, strict_keys: true)
** (Poison.EncodeError) duplicate key found: :foo
Benchmarking
$ MIX_ENV=bench mix run bench/run.exs
Current Benchmarks
As of 2020-06-25:
- Amazon EC2 c5.2xlarge instance running Ubuntu Server 20.04: https://gist.github.com/devinus/c82c2f6eaa22456e7ff0f5705466b1de
License
Poison is released under the public-domain-equivalent 0BSD license.