Skip to content

Commit

Permalink
vendor Jason
Browse files Browse the repository at this point in the history
This prevents module conflicts with the user's version of Jason since
both are loaded into the same beam instance (at least until #253) is
addressed.
  • Loading branch information
axelson authored and lukaszsamson committed Oct 30, 2022
1 parent 6a966cc commit e23c65b
Show file tree
Hide file tree
Showing 25 changed files with 464 additions and 307 deletions.
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Jason
# JasonVendored

A blazing fast JSON parser and generator in pure Elixir.

The parser and generator are at least twice as fast as other Elixir/Erlang libraries
(most notably `Poison`).
The performance is comparable to `jiffy`, which is implemented in C as a NIF.
Jason is usually only twice as slow.
JasonVendored is usually only twice as slow.

Both parser and generator fully conform to
[RFC 8259](https://tools.ietf.org/html/rfc8259) and
Expand All @@ -26,10 +26,10 @@ end
## Basic Usage

``` elixir
iex(1)> Jason.encode!(%{"age" => 44, "name" => "Steve Irwin", "nationality" => "Australian"})
iex(1)> JasonVendored.encode!(%{"age" => 44, "name" => "Steve Irwin", "nationality" => "Australian"})
"{\"age\":44,\"name\":\"Steve Irwin\",\"nationality\":\"Australian\"}"

iex(2)> Jason.decode!(~s({"age":44,"name":"Steve Irwin","nationality":"Australian"}))
iex(2)> JasonVendored.decode!(~s({"age":44,"name":"Steve Irwin","nationality":"Australian"}))
%{"age" => 44, "name" => "Steve Irwin", "nationality" => "Australian"}
```

Expand All @@ -39,17 +39,17 @@ Full documentation can be found at [https://hexdocs.pm/jason](https://hexdocs.pm

### Postgrex

Versions starting at 0.14.0 use `Jason` by default. For earlier versions, please refer to
Versions starting at 0.14.0 use `JasonVendored` by default. For earlier versions, please refer to
[previous versions of this document](https://github.com/michalmuskala/jason/tree/v1.1.2#postgrex).

### Ecto

Versions starting at 3.0.0 use `Jason` by default. For earlier versions, please refer to
Versions starting at 3.0.0 use `JasonVendored` by default. For earlier versions, please refer to
[previous versions of this document](https://github.com/michalmuskala/jason/tree/v1.1.2#ecto).

### Plug (and Phoenix)

Phoenix starting at 1.4.0 uses `Jason` by default. For earlier versions, please refer to
Phoenix starting at 1.4.0 uses `JasonVendored` by default. For earlier versions, please refer to
[previous versions of this document](https://github.com/michalmuskala/jason/tree/v1.1.2#plug-and-phoenix).

### Absinthe
Expand All @@ -60,12 +60,12 @@ You need to pass the `:json_codec` option to `Absinthe.Plug`
# When called directly:
plug Absinthe.Plug,
schema: MyApp.Schema,
json_codec: Jason
json_codec: JasonVendored

# When used in phoenix router:
forward "/api",
to: Absinthe.Plug,
init_opts: [schema: MyApp.Schema, json_codec: Jason]
init_opts: [schema: MyApp.Schema, json_codec: JasonVendored]
```

## Benchmarks
Expand All @@ -85,24 +85,24 @@ A HTML report of the benchmarks (after their execution) can be found in

## Differences to Poison

Jason has a couple feature differences compared to Poison.
JasonVendored has a couple feature differences compared to Poison.

* Jason follows the JSON spec more strictly, for example it does not allow
* JasonVendored follows the JSON spec more strictly, for example it does not allow
unescaped newline characters in JSON strings - e.g. `"\"\n\""` will
produce a decoding error.
* no support for decoding into data structures (the `as:` option).
* no built-in encoders for `MapSet`, `Range` and `Stream`.
* no support for encoding arbitrary structs - explicit implementation
of the `Jason.Encoder` protocol is always required.
of the `JasonVendored.Encoder` protocol is always required.
* different pretty-printing customisation options (default `pretty: true` works the same)

If you require encoders for any of the unsupported collection types, I suggest
adding the needed implementations directly to your project:

```elixir
defimpl Jason.Encoder, for: [MapSet, Range, Stream] do
defimpl JasonVendored.Encoder, for: [MapSet, Range, Stream] do
def encode(struct, opts) do
Jason.Encode.list(Enum.to_list(struct), opts)
JasonVendored.Encode.list(Enum.to_list(struct), opts)
end
end
```
Expand All @@ -112,7 +112,7 @@ if you own the struct, you can derive the implementation specifying
which fields should be encoded to JSON:

```elixir
@derive {Jason.Encoder, only: [....]}
@derive {JasonVendored.Encoder, only: [....]}
defstruct # ...
```

Expand All @@ -121,21 +121,21 @@ used carefully to avoid accidentally leaking private information
when new fields are added:

```elixir
@derive Jason.Encoder
@derive JasonVendored.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:

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

## License

Jason is released under the Apache License 2.0 - see the [LICENSE](LICENSE) file.
JasonVendored is released under the Apache License 2.0 - see the [LICENSE](LICENSE) file.

Some elements of tests and benchmarks have their origins in the
[Poison library](https://github.com/devinus/poison) and were initially licensed under [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
24 changes: 12 additions & 12 deletions bench/decode.exs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
decode_jobs = %{
"Jason" => fn {json, _} -> Jason.decode!(json) end,
"JasonVendored" => fn {json, _} -> JasonVendored.decode!(json) end,
"Poison" => fn {json, _} -> Poison.decode!(json) end,
"JSX" => fn {json, _} -> JSX.decode!(json, [:strict]) end,
"Tiny" => fn {json, _} -> Tiny.decode!(json) end,
"jsone" => fn {json, _} -> :jsone.decode(json) end,
"jiffy" => fn {json, _} -> :jiffy.decode(json, [:return_maps, :use_nil]) end,
"JSON" => fn {json, _} -> JSON.decode!(json) end,
"JSX" => fn {json, _} -> JSX.decode!(json, [:strict]) end,
"Tiny" => fn {json, _} -> Tiny.decode!(json) end,
"jsone" => fn {json, _} -> :jsone.decode(json) end,
"jiffy" => fn {json, _} -> :jiffy.decode(json, [:return_maps, :use_nil]) end,
"JSON" => fn {json, _} -> JSON.decode!(json) end
# "binary_to_term/1" => fn {_, etf} -> :erlang.binary_to_term(etf) end,
}

Expand All @@ -19,18 +19,18 @@ decode_inputs = [
"JSON Generator (Pretty)",
"UTF-8 escaped",
"UTF-8 unescaped",
"Issue 90",
"Issue 90"
]

read_data = fn (name) ->
read_data = fn name ->
file =
name
|> String.downcase
|> String.downcase()
|> String.replace(~r/([^\w]|-|_)+/, "-")
|> String.trim("-")

json = File.read!(Path.expand("data/#{file}.json", __DIR__))
etf = :erlang.term_to_binary(Jason.decode!(json))
etf = :erlang.term_to_binary(JasonVendored.decode!(json))

{json, etf}
end
Expand All @@ -45,7 +45,7 @@ end
IO.puts("\n")

Benchee.run(decode_jobs,
# parallel: 4,
# parallel: 4,
warmup: 5,
time: 30,
memory_time: 1,
Expand All @@ -54,6 +54,6 @@ Benchee.run(decode_jobs,
load: "output/runs/*.benchee",
formatters: [
{Benchee.Formatters.HTML, file: Path.expand("output/decode.html", __DIR__)},
Benchee.Formatters.Console,
Benchee.Formatters.Console
]
)
38 changes: 19 additions & 19 deletions bench/encode.exs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
encode_jobs = %{
"Jason" => &Jason.encode_to_iodata!/1,
"Jason strict" => &Jason.encode_to_iodata!(&1, maps: :strict),
"Poison" => &Poison.encode_to_iodata!/1,
"JSX" => &JSX.encode!/1,
"Tiny" => &Tiny.encode!/1,
"jsone" => &:jsone.encode/1,
"jiffy" => &:jiffy.encode/1,
"JSON" => &JSON.encode!/1,
"JasonVendored" => &JasonVendored.encode_to_iodata!/1,
"JasonVendored strict" => &JasonVendored.encode_to_iodata!(&1, maps: :strict),
"Poison" => &Poison.encode_to_iodata!/1,
"JSX" => &JSX.encode!/1,
"Tiny" => &Tiny.encode!/1,
"jsone" => &:jsone.encode/1,
"jiffy" => &:jiffy.encode/1,
"JSON" => &JSON.encode!/1
# "term_to_binary" => &:erlang.term_to_binary/1,
}

Expand All @@ -22,28 +22,28 @@ encode_inputs = [
"Canada",
]

read_data = fn (name) ->
read_data = fn name ->
name
|> String.downcase
|> String.downcase()
|> String.replace(~r/([^\w]|-|_)+/, "-")
|> String.trim("-")
|> (&"data/#{&1}.json").()
|> Path.expand(__DIR__)
|> File.read!
|> File.read!()
end


Benchee.run(encode_jobs,
# parallel: 4,
# parallel: 4,
warmup: 5,
time: 30,
memory_time: 1,
inputs: for name <- encode_inputs, into: %{} do
name
|> read_data.()
|> Jason.decode!()
|> (&{name, &1}).()
end,
inputs:
for name <- encode_inputs, into: %{} do
name
|> read_data.()
|> JasonVendored.decode!()
|> (&{name, &1}).()
end,
formatters: [
{Benchee.Formatters.HTML, file: Path.expand("output/encode.html", __DIR__)},
Benchee.Formatters.Console
Expand Down
2 changes: 1 addition & 1 deletion bench/mix.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule JasonBench.MixProject do
defmodule JasonVendoredBench.MixProject do
use Mix.Project

def project do
Expand Down
10 changes: 5 additions & 5 deletions dialyzer.ignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Unknown function 'Elixir.Jason.Encoder.Function':'__impl__'/1
Unknown function 'Elixir.Jason.Encoder.PID':'__impl__'/1
Unknown function 'Elixir.Jason.Encoder.Port':'__impl__'/1
Unknown function 'Elixir.Jason.Encoder.Reference':'__impl__'/1
Unknown function 'Elixir.Jason.Encoder.Tuple':'__impl__'/1
Unknown function 'Elixir.JasonVendored.Encoder.Function':'__impl__'/1
Unknown function 'Elixir.JasonVendored.Encoder.PID':'__impl__'/1
Unknown function 'Elixir.JasonVendored.Encoder.Port':'__impl__'/1
Unknown function 'Elixir.JasonVendored.Encoder.Reference':'__impl__'/1
Unknown function 'Elixir.JasonVendored.Encoder.Tuple':'__impl__'/1
14 changes: 7 additions & 7 deletions lib/codegen.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Jason.Codegen do
defmodule JasonVendored.Codegen do
@moduledoc false

alias Jason.{Encode, EncodeError}
alias JasonVendored.{Encode, EncodeError}

def jump_table(ranges, default) do
ranges
Expand Down Expand Up @@ -93,12 +93,12 @@ defmodule Jason.Codegen do
defp ranges_to_orddict(ranges) do
ranges
|> Enum.flat_map(fn
{int, value} when is_integer(int) ->
[{int, value}]
{int, value} when is_integer(int) ->
[{int, value}]

{enum, value} ->
Enum.map(enum, &{&1, value})
end)
{enum, value} ->
Enum.map(enum, &{&1, value})
end)
|> :orddict.from_list()
end

Expand Down
Loading

0 comments on commit e23c65b

Please sign in to comment.