changed
LICENSE
|
@@ -1,4 1,6 @@
|
1
|
- Copyright (C) 2021 Devin Alexander Torres <[email protected]>
|
1
|
BSD Zero Clause License
|
2
|
|
3
|
Copyright (C) 2024 Devin Alexander Torres <[email protected]>
|
2
4
|
|
3
5
|
Permission to use, copy, modify, and/or distribute this software for any
|
4
6
|
purpose with or without fee is hereby granted.
|
changed
README.md
|
@@ -1,9 1,10 @@
|
1
1
|
# Poison
|
2
2
|
|
3
|
- [![Build Status](https://travis-ci.org/devinus/poison.svg?branch=master)](https://travis-ci.org/devinus/poison)
|
4
|
- [![Coverage Status](https://coveralls.io/repos/github/devinus/poison/badge.svg?branch=master)](https://coveralls.io/github/devinus/poison?branch=master)
|
5
|
- [![Hex.pm Version](https://img.shields.io/hexpm/v/poison.svg?style=flat-square)](https://hex.pm/packages/poison)
|
6
|
- [![Hex.pm Download Total](https://img.shields.io/hexpm/dt/poison.svg?style=flat-square)](https://hex.pm/packages/poison)
|
3
|
[![Build Status](https://img.shields.io/github/actions/workflow/status/devinus/poison/ci.yml)](https://github.com/devinus/poison/actions/workflows/ci.yml)
|
4
|
[![Coverage Status](https://img.shields.io/coverallsCoverage/github/devinus/poison)](https://coveralls.io/github/devinus/poison?branch=master))
|
5
|
[![Hex.pm Version](https://img.shields.io/hexpm/v/poison)](https://hex.pm/packages/poison)
|
6
|
[![Hex.pm Download Total](https://img.shields.io/hexpm/dt/poison)](https://hex.pm/packages/poison)
|
7
|
[![Hex.pm Dependents](https://img.shields.io/librariesio/dependents/hex/poison)](https://hex.pm/packages/poison)
|
7
8
|
|
8
9
|
Poison is a new JSON library for Elixir focusing on wicked-fast **speed**
|
9
10
|
without sacrificing **simplicity**, **completeness**, or **correctness**.
|
|
@@ -17,7 18,7 @@ several techniques that are [known to benefit BeamAsm][2] for JIT compilation,
|
17
18
|
Poison benchmarks sometimes puts Poison's performance close to `jiffy` and
|
18
19
|
usually faster than other Erlang/Elixir libraries.
|
19
20
|
|
20
|
- Poison fully conforms to [RFC 7159][4], [ECMA 404][5], and fully passes the
|
21
|
Poison fully conforms to [RFC 8259][4], [ECMA 404][5], and fully passes the
|
21
22
|
[JSONTestSuite][6].
|
22
23
|
|
23
24
|
## Installation
|
|
@@ -26,14 27,14 @@ First, add Poison to your `mix.exs` dependencies:
|
26
27
|
|
27
28
|
```elixir
|
28
29
|
def deps do
|
29
|
- [{:poison, "~> 5.0"}]
|
30
|
[{:poison, "~> 6.0"}]
|
30
31
|
end
|
31
32
|
```
|
32
33
|
|
33
34
|
Then, update your dependencies:
|
34
35
|
|
35
|
- ```sh-session
|
36
|
- $ mix deps.get
|
36
|
```sh
|
37
|
mix deps.get
|
37
38
|
```
|
38
39
|
|
39
40
|
## Usage
|
|
@@ -133,7 134,7 @@ ignored.
|
133
134
|
|
134
135
|
### Key Validation
|
135
136
|
|
136
|
- According to [RFC 7159][4] keys in a JSON object should be unique. This is
|
137
|
According to [RFC 8259][4] keys in a JSON object should be unique. This is
|
137
138
|
enforced and resolved in different ways in other libraries. In the Ruby JSON
|
138
139
|
library for example, the output generated from encoding a hash with a duplicate
|
139
140
|
key (say one is a string, the other an atom) will include both keys. When
|
|
@@ -154,27 155,27 @@ iex> Poison.encode!(%{:foo => "foo1", "foo" => "foo2"}, strict_keys: true)
|
154
155
|
|
155
156
|
## Benchmarking
|
156
157
|
|
157
|
- ```sh-session
|
158
|
- $ MIX_ENV=bench mix run bench/run.exs
|
158
|
```sh
|
159
|
MIX_ENV=bench mix run bench/run.exs
|
159
160
|
```
|
160
161
|
|
161
162
|
### Current Benchmarks
|
162
163
|
|
163
|
- As of 2020-06-25:
|
164
|
As of 2024-06-06:
|
164
165
|
|
165
|
- - Amazon EC2 c5.2xlarge instance running Ubuntu Server 20.04:
|
166
|
- https://gist.github.com/devinus/c82c2f6eaa22456e7ff0f5705466b1de
|
166
|
- Amazon EC2 c6i.2xlarge instance running Ubuntu Server 22.04:
|
167
|
<https://gist.github.com/devinus/afb351ae45194a6b93b6db9bf2d4c163>
|
167
168
|
|
168
169
|
## License
|
169
170
|
|
170
171
|
Poison is released under the [public-domain-equivalent][8] [0BSD][9] license.
|
171
172
|
|
172
|
- [1]: http://www.erlang.org/euc/07/papers/1700Gustafsson.pdf
|
173
|
[1]: https://erlang.org/euc/07/papers/1700Gustafsson.pdf
|
173
174
|
[2]: https://erlang.org/documentation/doc-12.0-rc1/erts-12.0/doc/html/BeamAsm.html
|
174
|
- [3]: http://jlouisramblings.blogspot.com/2013/07/problematic-traits-in-erlang.html
|
175
|
- [4]: https://tools.ietf.org/html/rfc7159
|
176
|
- [5]: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
175
|
[3]: https://jlouisramblings.blogspot.com/2013/07/problematic-traits-in-erlang.html
|
176
|
[4]: https://datatracker.ietf.org/doc/html/rfc8259
|
177
|
[5]: https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf
|
177
178
|
[6]: https://github.com/nst/JSONTestSuite
|
178
|
- [7]: http://prog21.dadgum.com/70.html
|
179
|
[7]: https://prog21.dadgum.com/70.html
|
179
180
|
[8]: https://en.wikipedia.org/wiki/Public-domain-equivalent_license
|
180
181
|
[9]: https://opensource.org/licenses/0BSD
|
changed
VERSION
|
@@ -1 1 @@
|
1
|
- 5.0.0
|
1
|
6.0.0
|
changed
hex_metadata.config
|
@@ -1,18 1,18 @@
|
1
|
- {<<"app">>,<<"poison">>}.
|
2
|
- {<<"build_tools">>,[<<"mix">>]}.
|
3
|
- {<<"description">>,<<"An incredibly fast, pure Elixir JSON library">>}.
|
4
|
- {<<"elixir">>,<<"~> 1.11">>}.
|
5
|
- {<<"files">>,
|
6
|
- [<<"lib">>,<<"lib/poison.ex">>,<<"lib/poison">>,<<"lib/poison/encoder.ex">>,
|
7
|
- <<"lib/poison/decoder.ex">>,<<"lib/poison/parser.ex">>,<<"mix.exs">>,
|
8
|
- <<"README.md">>,<<"LICENSE">>,<<"VERSION">>]}.
|
9
|
- {<<"licenses">>,[<<"0BSD">>]}.
|
10
1
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/devinus/poison">>}]}.
|
11
2
|
{<<"name">>,<<"poison">>}.
|
3
|
{<<"version">>,<<"6.0.0">>}.
|
4
|
{<<"description">>,<<"An incredibly fast, pure Elixir JSON library">>}.
|
5
|
{<<"elixir">>,<<"~> 1.12">>}.
|
6
|
{<<"app">>,<<"poison">>}.
|
7
|
{<<"files">>,
|
8
|
[<<"lib">>,<<"lib/poison">>,<<"lib/poison/decoder.ex">>,
|
9
|
<<"lib/poison/encoder.ex">>,<<"lib/poison/parser.ex">>,<<"lib/poison.ex">>,
|
10
|
<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"VERSION">>]}.
|
11
|
{<<"licenses">>,[<<"0BSD">>]}.
|
12
12
|
{<<"requirements">>,
|
13
|
- [[{<<"app">>,<<"decimal">>},
|
14
|
- {<<"name">>,<<"decimal">>},
|
13
|
[[{<<"name">>,<<"decimal">>},
|
14
|
{<<"app">>,<<"decimal">>},
|
15
15
|
{<<"optional">>,true},
|
16
|
- {<<"repository">>,<<"hexpm">>},
|
17
|
- {<<"requirement">>,<<"~> 2.0">>}]]}.
|
18
|
- {<<"version">>,<<"5.0.0">>}.
|
16
|
{<<"requirement">>,<<"~> 2.1">>},
|
17
|
{<<"repository">>,<<"hexpm">>}]]}.
|
18
|
{<<"build_tools">>,[<<"mix">>]}.
|
changed
lib/poison.ex
|
@@ -8,15 8,58 @@ defmodule Poison do
|
8
8
|
alias Poison.{EncodeError, Encoder}
|
9
9
|
alias Poison.{ParseError, Parser}
|
10
10
|
|
11
|
@doc """
|
12
|
Encode a value to JSON IO data.
|
13
|
|
14
|
iex> Poison.encode_to_iodata([1, 2, 3])
|
15
|
{:ok, [91, ["1", 44, "2", 44, "3"], 93]}
|
16
|
|
17
|
iex> Poison.encode_to_iodata({})
|
18
|
{:error, %Poison.EncodeError{message: nil, value: {}}}
|
19
|
"""
|
20
|
@spec encode_to_iodata(Encoder.t()) :: {:ok, iodata} | {:error, EncodeError.t()}
|
21
|
@spec encode_to_iodata(Encoder.t(), Encoder.options() | [Encoder.option()]) ::
|
22
|
{:ok, iodata} | {:error, EncodeError.t()}
|
23
|
def encode_to_iodata(value, options \\ %{}) do
|
24
|
{:ok, encode_to_iodata!(value, options)}
|
25
|
rescue
|
26
|
exception in [EncodeError] ->
|
27
|
{:error, exception}
|
28
|
end
|
29
|
|
30
|
@doc """
|
31
|
Encode a value to JSON IO data. Raises an exception on error.
|
32
|
|
33
|
iex> Poison.encode_to_iodata!([1, 2, 3])
|
34
|
[91, ["1", 44, "2", 44, "3"], 93]
|
35
|
|
36
|
iex> Poison.encode_to_iodata!({})
|
37
|
** (Poison.EncodeError) unable to encode value: {}
|
38
|
"""
|
39
|
@spec encode_to_iodata!(Encoder.t()) :: iodata
|
40
|
@spec encode_to_iodata!(Encoder.t(), Encoder.options() | [Encoder.option()]) :: iodata
|
41
|
def encode_to_iodata!(value, options \\ %{})
|
42
|
|
43
|
def encode_to_iodata!(value, options) when is_list(options) do
|
44
|
encode_to_iodata!(value, Map.new(options))
|
45
|
end
|
46
|
|
47
|
def encode_to_iodata!(value, options) do
|
48
|
Encoder.encode(value, options)
|
49
|
end
|
50
|
|
11
51
|
@doc """
|
12
52
|
Encode a value to JSON.
|
13
53
|
|
14
54
|
iex> Poison.encode([1, 2, 3])
|
15
55
|
{:ok, "[1,2,3]"}
|
56
|
|
57
|
iex> Poison.encode({})
|
58
|
{:error, %Poison.EncodeError{message: nil, value: {}}}
|
16
59
|
"""
|
17
|
- @spec encode(Encoder.t(), Encoder.options()) ::
|
18
|
- {:ok, iodata}
|
19
|
- | {:error, Exception.t()}
|
60
|
@spec encode(Encoder.t()) :: {:ok, iodata} | {:error, EncodeError.t()}
|
61
|
@spec encode(Encoder.t(), Encoder.options() | [Encoder.option()]) ::
|
62
|
{:ok, iodata} | {:error, EncodeError.t()}
|
20
63
|
def encode(value, options \\ %{}) do
|
21
64
|
{:ok, encode!(value, options)}
|
22
65
|
rescue
|
|
@@ -25,12 68,16 @@ defmodule Poison do
|
25
68
|
end
|
26
69
|
|
27
70
|
@doc """
|
28
|
- Encode a value to JSON, raises an exception on error.
|
71
|
Encode a value to JSON. Raises an exception on error.
|
29
72
|
|
30
73
|
iex> Poison.encode!([1, 2, 3])
|
31
74
|
"[1,2,3]"
|
75
|
|
76
|
iex> Poison.encode!({})
|
77
|
** (Poison.EncodeError) unable to encode value: {}
|
32
78
|
"""
|
33
|
- @spec encode!(Encoder.t(), Encoder.options()) :: iodata | no_return
|
79
|
@spec encode!(Encoder.t()) :: binary
|
80
|
@spec encode!(Encoder.t(), Encoder.options() | [Encoder.option()]) :: binary
|
34
81
|
def encode!(value, options \\ %{})
|
35
82
|
|
36
83
|
def encode!(value, options) when is_list(options) do
|
|
@@ -38,11 85,7 @@ defmodule Poison do
|
38
85
|
end
|
39
86
|
|
40
87
|
def encode!(value, options) do
|
41
|
- if options[:iodata] do
|
42
|
- Encoder.encode(value, options)
|
43
|
- else
|
44
|
- value |> Encoder.encode(options) |> IO.iodata_to_binary()
|
45
|
- end
|
88
|
value |> encode_to_iodata!(options) |> IO.iodata_to_binary()
|
46
89
|
end
|
47
90
|
|
48
91
|
@doc """
|
|
@@ -50,13 93,13 @@ defmodule Poison do
|
50
93
|
|
51
94
|
iex> Poison.decode("[1,2,3]")
|
52
95
|
{:ok, [1, 2, 3]}
|
96
|
|
97
|
iex> Poison.decode("[")
|
98
|
{:error, %Poison.ParseError{data: "[", skip: 1, value: nil}}
|
53
99
|
"""
|
54
|
- @spec decode(iodata) ::
|
55
|
- {:ok, Parser.t()}
|
56
|
- | {:error, Exception.t()}
|
57
|
- @spec decode(iodata, Decoder.options()) ::
|
58
|
- {:ok, Parser.t()}
|
59
|
- | {:error, Exception.t()}
|
100
|
@spec decode(iodata) :: {:ok, Parser.t()} | {:error, ParseError.t() | DecodeError.t()}
|
101
|
@spec decode(iodata, Decoder.options() | [Decoder.option()]) ::
|
102
|
{:ok, Decoder.t()} | {:error, ParseError.t() | DecodeError.t()}
|
60
103
|
def decode(iodata, options \\ %{}) do
|
61
104
|
{:ok, decode!(iodata, options)}
|
62
105
|
rescue
|
|
@@ -65,17 108,18 @@ defmodule Poison do
|
65
108
|
end
|
66
109
|
|
67
110
|
@doc """
|
68
|
- Decode JSON to a value, raises an exception on error.
|
111
|
Decode JSON to a value. Raises an exception on error.
|
69
112
|
|
70
113
|
iex> Poison.decode!("[1,2,3]")
|
71
114
|
[1, 2, 3]
|
72
|
- """
|
73
|
- @spec decode!(iodata) :: Parser.t() | no_return
|
74
|
- def decode!(value) do
|
75
|
- Parser.parse!(value, %{})
|
76
|
- end
|
77
115
|
|
78
|
- @spec decode!(iodata, Decoder.options()) :: Decoder.t() | no_return
|
116
|
iex> Poison.decode!("[")
|
117
|
** (Poison.ParseError) unexpected end of input at position 1
|
118
|
"""
|
119
|
@spec decode!(iodata) :: Parser.t()
|
120
|
@spec decode!(iodata, Decoder.options() | [Decoder.option()]) :: Decoder.t()
|
121
|
def decode!(value, options \\ %{})
|
122
|
|
79
123
|
def decode!(value, options) when is_list(options) do
|
80
124
|
decode!(value, Map.new(options))
|
81
125
|
end
|
|
@@ -84,7 128,6 @@ defmodule Poison do
|
84
128
|
value
|
85
129
|
|> Parser.parse!(options)
|
86
130
|
|> Decode.transform(options)
|
87
|
- |> Decoder.decode(options)
|
88
131
|
end
|
89
132
|
|
90
133
|
def decode!(value, options) do
|
changed
lib/poison/decoder.ex
|
@@ -17,6 17,9 @@ defmodule Poison.Decode do
|
17
17
|
|
18
18
|
alias Poison.Decoder
|
19
19
|
|
20
|
@compile :inline
|
21
|
@compile :inline_list_funcs
|
22
|
|
20
23
|
def transform(value, options) when is_map(value) or is_list(value) do
|
21
24
|
case Map.get(options, :as) do
|
22
25
|
nil -> value
|
|
@@ -42,6 45,10 @@ defmodule Poison.Decode do
|
42
45
|
for v <- value, do: transform(v, keys, as, options)
|
43
46
|
end
|
44
47
|
|
48
|
defp transform(value, keys, as, options) when is_function(as, 1) do
|
49
|
transform(value, keys, as.(value), options)
|
50
|
end
|
51
|
|
45
52
|
defp transform(value, _keys, _as, _options) do
|
46
53
|
value
|
47
54
|
end
|
|
@@ -106,11 113,14 @@ end
|
106
113
|
defprotocol Poison.Decoder do
|
107
114
|
@fallback_to_any true
|
108
115
|
|
109
|
- @typep as :: map | struct | [as]
|
116
|
@type keys :: :atoms | :atoms!
|
117
|
@type decimal :: boolean
|
118
|
@type as :: map | struct | [as] | (t -> as)
|
110
119
|
|
120
|
@type option :: {:keys, keys} | {:decimal, decimal} | {:as, as}
|
111
121
|
@type options :: %{
|
112
|
- optional(:keys) => :atoms | :atoms!,
|
113
|
- optional(:decimal) => boolean,
|
122
|
optional(:keys) => keys,
|
123
|
optional(:decimal) => decimal,
|
114
124
|
optional(:as) => as
|
115
125
|
}
|
changed
lib/poison/encoder.ex
|
@@ -80,11 80,18 @@ end
|
80
80
|
defprotocol Poison.Encoder do
|
81
81
|
@fallback_to_any true
|
82
82
|
|
83
|
- @typep escape :: :unicode | :javascript | :html_safe
|
84
|
- @typep pretty :: boolean
|
85
|
- @typep indent :: non_neg_integer
|
86
|
- @typep offset :: non_neg_integer
|
87
|
- @typep strict_keys :: boolean
|
83
|
@type escape :: :unicode | :javascript | :html_safe
|
84
|
@type pretty :: boolean
|
85
|
@type indent :: non_neg_integer
|
86
|
@type offset :: non_neg_integer
|
87
|
@type strict_keys :: boolean
|
88
|
|
89
|
@type option ::
|
90
|
{:escape, escape}
|
91
|
| {:pretty, pretty}
|
92
|
| {:indent, indent}
|
93
|
| {:offset, offset}
|
94
|
| {:strict_keys, strict_keys}
|
88
95
|
|
89
96
|
@type options :: %{
|
90
97
|
optional(:escape) => escape,
|
|
@@ -109,7 116,7 @@ defimpl Poison.Encoder, for: Atom do
|
109
116
|
end
|
110
117
|
|
111
118
|
defimpl Poison.Encoder, for: BitString do
|
112
|
- use Bitwise
|
119
|
import Bitwise
|
113
120
|
|
114
121
|
@compile :inline
|
115
122
|
@compile :inline_list_funcs
|
|
@@ -130,7 137,7 @@ defimpl Poison.Encoder, for: BitString do
|
130
137
|
end
|
131
138
|
end
|
132
139
|
|
133
|
- # http://en.wikipedia.org/wiki/Unicode_control_characters
|
140
|
# https://en.wikipedia.org/wiki/Unicode_control_characters
|
134
141
|
defp escape(<<char, rest::bits>>, mode) when char <= 0x1F or char == 0x7F do
|
135
142
|
[seq(char) | escape(rest, mode)]
|
136
143
|
end
|
|
@@ -147,8 154,8 @@ defimpl Poison.Encoder, for: BitString do
|
147
154
|
[seq(char) | escape(rest, :unicode)]
|
148
155
|
end
|
149
156
|
|
150
|
- # http://en.wikipedia.org/wiki/UTF-16#Example_UTF-16_encoding_procedure
|
151
|
- # http://unicodebook.readthedocs.org/unicode_encodings.html
|
157
|
# https://en.wikipedia.org/wiki/UTF-16#U D800_to_U DFFF_(surrogates)
|
158
|
# https://unicodebook.readthedocs.io/unicode_encodings.html
|
152
159
|
defp escape(<<char::utf8, rest::bits>>, :unicode) when char > 0xFFFF do
|
153
160
|
code = char - 0x10000
|
154
161
|
|
|
@@ -159,13 166,28 @@ defimpl Poison.Encoder, for: BitString do
|
159
166
|
]
|
160
167
|
end
|
161
168
|
|
162
|
- defp escape(<<char::utf8, rest::bits>>, mode)
|
163
|
- when mode in [:html_safe, :javascript] and char in [0x2028, 0x2029] do
|
164
|
- [seq(char) | escape(rest, mode)]
|
169
|
# https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
|
170
|
# https://rubydoc.info/docs/rails/ActiveSupport/JSON/Encoding.escape_html_entities_in_json=
|
171
|
defp escape(<<?&::utf8, rest::bits>>, :html_safe) do
|
172
|
["\\u0026" | escape(rest, :html_safe)]
|
165
173
|
end
|
166
174
|
|
167
|
- defp escape(<<?/::utf8, rest::bits>>, :html_safe) do
|
168
|
- ["\\/" | escape(rest, :html_safe)]
|
175
|
defp escape(<<?<::utf8, rest::bits>>, :html_safe) do
|
176
|
["\\u003C" | escape(rest, :html_safe)]
|
177
|
end
|
178
|
|
179
|
defp escape(<<?>::utf8, rest::bits>>, :html_safe) do
|
180
|
["\\u003E" | escape(rest, :html_safe)]
|
181
|
end
|
182
|
|
183
|
defp escape(<<"\u2028"::utf8, rest::bits>>, mode)
|
184
|
when mode in [:html_safe, :javascript] do
|
185
|
["\\u2028" | escape(rest, mode)]
|
186
|
end
|
187
|
|
188
|
defp escape(<<"\u2029"::utf8, rest::bits>>, mode)
|
189
|
when mode in [:html_safe, :javascript] do
|
190
|
["\\u2029" | escape(rest, mode)]
|
169
191
|
end
|
170
192
|
|
171
193
|
defp escape(string, mode) do
|
|
@@ -177,11 199,11 @@ defimpl Poison.Encoder, for: BitString do
|
177
199
|
@compile {:inline, chunk_size: 3}
|
178
200
|
|
179
201
|
defp chunk_size(<<char, _rest::bits>>, _mode, acc)
|
180
|
- when char <= 0x1F or char in '"\\' do
|
202
|
when char <= 0x1F or char in ~c("\\) do
|
181
203
|
acc
|
182
204
|
end
|
183
205
|
|
184
|
- defp chunk_size(<<?/, _rest::bits>>, :html_safe, acc) do
|
206
|
defp chunk_size(<<char, _rest::bits>>, :html_safe, acc) when char in ~c(&<>) do
|
185
207
|
acc
|
186
208
|
end
|
187
209
|
|
|
@@ -189,7 211,7 @@ defimpl Poison.Encoder, for: BitString do
|
189
211
|
chunk_size(rest, mode, acc 1)
|
190
212
|
end
|
191
213
|
|
192
|
- defp chunk_size(<<_::utf8, _rest::bits>>, :unicode, acc) do
|
214
|
defp chunk_size(<<_codepoint::utf8, _rest::bits>>, :unicode, acc) do
|
193
215
|
acc
|
194
216
|
end
|
195
217
|
|
|
@@ -199,7 221,7 @@ defimpl Poison.Encoder, for: BitString do
|
199
221
|
end
|
200
222
|
|
201
223
|
defp chunk_size(<<codepoint::utf8, rest::bits>>, mode, acc) do
|
202
|
- chunk_size(rest, mode, acc byte_size(<<codepoint::utf8>>))
|
224
|
chunk_size(rest, mode, acc codepoint_size(codepoint))
|
203
225
|
end
|
204
226
|
|
205
227
|
defp chunk_size(<<>>, _mode, acc), do: acc
|
|
@@ -218,6 240,16 @@ defimpl Poison.Encoder, for: BitString do
|
218
240
|
s -> ["\\u" | s]
|
219
241
|
end
|
220
242
|
end
|
243
|
|
244
|
@compile {:inline, codepoint_size: 1}
|
245
|
|
246
|
defp codepoint_size(codepoint) do
|
247
|
cond do
|
248
|
codepoint <= 0x7FF -> 2
|
249
|
codepoint <= 0xFFFF -> 3
|
250
|
true -> 4
|
251
|
end
|
252
|
end
|
221
253
|
end
|
222
254
|
|
223
255
|
defimpl Poison.Encoder, for: Integer do
|
|
@@ -240,7 272,7 @@ defimpl Poison.Encoder, for: Map do
|
240
272
|
@compile :inline
|
241
273
|
@compile :inline_list_funcs
|
242
274
|
|
243
|
- def encode(map, _) when map_size(map) < 1, do: "{}"
|
275
|
def encode(map, _options) when map_size(map) < 1, do: "{}"
|
244
276
|
|
245
277
|
def encode(map, options) do
|
246
278
|
map
|
|
@@ -347,7 379,7 @@ defimpl Poison.Encoder, for: List do
|
347
379
|
end
|
348
380
|
end
|
349
381
|
|
350
|
- defimpl Poison.Encoder, for: [Range, Stream, MapSet, HashSet] do
|
382
|
defimpl Poison.Encoder, for: [Range, Stream, MapSet, Date.Range] do
|
351
383
|
alias Poison.{Encoder, Pretty}
|
352
384
|
|
353
385
|
use Pretty
|
changed
lib/poison/parser.ex
|
@@ -30,7 30,7 @@ defmodule Poison.ParseError do
|
30
30
|
<<>> ->
|
31
31
|
"unexpected end of input at position #{pos}"
|
32
32
|
|
33
|
- <<token::utf8, _::bits>> ->
|
33
|
<<token::utf8, _rest::bits>> ->
|
34
34
|
"unexpected token at position #{pos}: #{escape(token)}"
|
35
35
|
|
36
36
|
_rest ->
|
|
@@ -50,31 50,28 @@ end
|
50
50
|
|
51
51
|
defmodule Poison.Parser do
|
52
52
|
@moduledoc """
|
53
|
- An RFC 7159 and ECMA 404 conforming JSON parser.
|
53
|
An RFC 8259 and ECMA 404 conforming JSON parser.
|
54
54
|
|
55
|
- See: https://tools.ietf.org/html/rfc7159
|
56
|
- See: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
|
55
|
See: https://datatracker.ietf.org/doc/html/rfc8259
|
56
|
See: https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf
|
57
57
|
"""
|
58
58
|
|
59
59
|
@compile :inline
|
60
60
|
@compile :inline_list_funcs
|
61
|
- @compile {:inline_effort, 2500}
|
62
|
- @compile {:inline_size, 150}
|
63
|
- @compile {:inline_unroll, 3}
|
64
61
|
|
65
|
- use Bitwise
|
62
|
import Bitwise
|
66
63
|
|
67
64
|
alias Poison.{Decoder, ParseError}
|
68
65
|
|
69
|
- @typep value :: nil | true | false | map | list | float | integer | String.t()
|
66
|
@type scalar :: nil | true | false | float | integer | String.t()
|
70
67
|
|
71
68
|
if Code.ensure_loaded?(Decimal) do
|
72
|
- @type t :: value | Decimal.t()
|
69
|
@type t :: scalar | Decimal.t() | [t] | %{optional(String.t()) => t}
|
73
70
|
else
|
74
|
- @type t :: value
|
71
|
@type t :: scalar | [t] | %{optional(String.t()) => t}
|
75
72
|
end
|
76
73
|
|
77
|
- whitespace = '\s\t\n\r'
|
74
|
whitespace = ~c"\s\t\n\r"
|
78
75
|
digits = ?0..?9
|
79
76
|
|
80
77
|
defmacrop syntax_error(skip) do
|
|
@@ -83,14 80,14 @@ defmodule Poison.Parser do
|
83
80
|
end
|
84
81
|
end
|
85
82
|
|
86
|
- @spec parse!(iodata | binary, Decoder.options()) :: t | no_return
|
83
|
@spec parse!(iodata, Decoder.options()) :: t
|
87
84
|
def parse!(value, options \\ %{})
|
88
85
|
|
89
86
|
def parse!(data, options) when is_bitstring(data) do
|
90
87
|
[value | skip] =
|
91
88
|
value(data, data, :maps.get(:keys, options, nil), :maps.get(:decimal, options, nil), 0)
|
92
89
|
|
93
|
- <<_::binary-size(skip), rest::bits>> = data
|
90
|
<<_skip::binary-size(skip), rest::bits>> = data
|
94
91
|
skip_whitespace(rest, skip, value)
|
95
92
|
rescue
|
96
93
|
exception in ParseError ->
|
|
@@ -105,25 102,16 @@ defmodule Poison.Parser do
|
105
102
|
|
106
103
|
@compile {:inline, value: 5}
|
107
104
|
|
108
|
- defp value(<<?f, rest::bits>>, _data, _keys, _decimal, skip) do
|
109
|
- case rest do
|
110
|
- <<"alse", _rest::bits>> -> [false | skip 5]
|
111
|
- _other -> syntax_error(skip)
|
112
|
- end
|
105
|
defp value(<<"null", _rest::bits>>, _data, _keys, _decimal, skip) do
|
106
|
[nil | skip 4]
|
113
107
|
end
|
114
108
|
|
115
|
- defp value(<<?t, rest::bits>>, _data, _keys, _decimal, skip) do
|
116
|
- case rest do
|
117
|
- <<"rue", _rest::bits>> -> [true | skip 4]
|
118
|
- _other -> syntax_error(skip)
|
119
|
- end
|
109
|
defp value(<<"true", _rest::bits>>, _data, _keys, _decimal, skip) do
|
110
|
[true | skip 4]
|
120
111
|
end
|
121
112
|
|
122
|
- defp value(<<?n, rest::bits>>, _data, _keys, _decimal, skip) do
|
123
|
- case rest do
|
124
|
- <<"ull", _rest::bits>> -> [nil | skip 4]
|
125
|
- _other -> syntax_error(skip)
|
126
|
- end
|
113
|
defp value(<<"false", _rest::bits>>, _data, _keys, _decimal, skip) do
|
114
|
[false | skip 5]
|
127
115
|
end
|
128
116
|
|
129
117
|
defp value(<<?-, rest::bits>>, _data, _keys, decimal, skip) do
|
|
@@ -192,10 180,10 @@ defmodule Poison.Parser do
|
192
180
|
defp object_pairs(<<?", rest::bits>>, data, keys, decimal, skip, acc) do
|
193
181
|
start = skip 1
|
194
182
|
[name | skip] = string_continue(rest, data, start)
|
195
|
- <<_::binary-size(skip), rest::bits>> = data
|
183
|
<<_skip::binary-size(skip), rest::bits>> = data
|
196
184
|
|
197
185
|
[value | skip] = object_value(rest, data, keys, decimal, skip)
|
198
|
- <<_::binary-size(skip), rest::bits>> = data
|
186
|
<<_skip::binary-size(skip), rest::bits>> = data
|
199
187
|
|
200
188
|
object_pairs_continue(rest, data, keys, decimal, skip, [
|
201
189
|
{object_name(keys, start, name), value} | acc
|
|
@@ -256,8 244,8 @@ defmodule Poison.Parser do
|
256
244
|
|
257
245
|
@compile {:inline, array_values: 6}
|
258
246
|
|
259
|
- defp array_values(<<?], _rest::bits>>, _data, _keys, _decimal, skip, _acc) do
|
260
|
- [[] | skip 1]
|
247
|
defp array_values(<<?], _rest::bits>>, _data, _keys, _decimal, skip, acc) do
|
248
|
[acc | skip 1]
|
261
249
|
end
|
262
250
|
|
263
251
|
for char <- whitespace do
|
|
@@ -268,7 256,7 @@ defmodule Poison.Parser do
|
268
256
|
|
269
257
|
defp array_values(rest, data, keys, decimal, skip, acc) do
|
270
258
|
[value | skip] = value(rest, data, keys, decimal, skip)
|
271
|
- <<_::binary-size(skip), rest::bits>> = data
|
259
|
<<_skip::binary-size(skip), rest::bits>> = data
|
272
260
|
array_values_continue(rest, data, keys, decimal, skip, [value | acc])
|
273
261
|
end
|
274
262
|
|
|
@@ -276,7 264,7 @@ defmodule Poison.Parser do
|
276
264
|
|
277
265
|
defp array_values_continue(<<?,, rest::bits>>, data, keys, decimal, skip, acc) do
|
278
266
|
[value | skip] = value(rest, data, keys, decimal, skip 1)
|
279
|
- <<_::binary-size(skip), rest::bits>> = data
|
267
|
<<_skip::binary-size(skip), rest::bits>> = data
|
280
268
|
array_values_continue(rest, data, keys, decimal, skip, [value | acc])
|
281
269
|
end
|
282
270
|
|
|
@@ -352,7 340,7 @@ defmodule Poison.Parser do
|
352
340
|
|
353
341
|
@compile {:inline, number_exp: 6}
|
354
342
|
|
355
|
- for e <- 'eE' do
|
343
|
for e <- ~c(eE) do
|
356
344
|
defp number_exp(<<unquote(e), rest::bits>>, decimal, skip, sign, coef, exp) do
|
357
345
|
[value | skip] = number_exp_continue(rest, skip 1)
|
358
346
|
number_complete(decimal, skip, sign, coef, exp value)
|
|
@@ -424,7 412,7 @@ defmodule Poison.Parser do
|
424
412
|
|
425
413
|
# See: https://arxiv.org/pdf/2101.11408.pdf
|
426
414
|
defp number_complete(_decimal, skip, sign, coef, exp)
|
427
|
- when exp in -10..10 and coef <= unquote(max_sig) do
|
415
|
when exp in -22..22 and coef <= unquote(max_sig) do
|
428
416
|
if exp < 0 do
|
429
417
|
[coef / pow10(-exp) * sign | skip]
|
430
418
|
else
|
|
@@ -440,7 428,7 @@ defmodule Poison.Parser do
|
440
428
|
| skip
|
441
429
|
]
|
442
430
|
rescue
|
443
|
- ArithmeticError ->
|
431
|
ArgumentError ->
|
444
432
|
reraise ParseError, [skip: skip, value: "#{coef * sign}e#{exp}"], __STACKTRACE__
|
445
433
|
end
|
446
434
|
|
|
@@ -486,10 474,13 @@ defmodule Poison.Parser do
|
486
474
|
end
|
487
475
|
|
488
476
|
unicode ->
|
489
|
- [
|
490
|
- :unicode.characters_to_binary([acc | binary_part(data, skip, len)], :utf8)
|
491
|
- | skip len 1
|
492
|
- ]
|
477
|
case :unicode.characters_to_binary([acc | binary_part(data, skip, len)], :utf8) do
|
478
|
string when is_binary(string) ->
|
479
|
[string | skip len 1]
|
480
|
|
481
|
_other ->
|
482
|
syntax_error(skip len)
|
483
|
end
|
493
484
|
|
494
485
|
true ->
|
495
486
|
[IO.iodata_to_binary([acc | binary_part(data, skip, len)]) | skip len 1]
|
|
@@ -515,29 506,23 @@ defmodule Poison.Parser do
|
515
506
|
|
516
507
|
@compile {:inline, string_escape: 5}
|
517
508
|
|
518
|
- for {seq, char} <- Enum.zip(~C("\ntr/fb), ~c("\\\n\t\r/\f\b)) do
|
519
|
- defp string_escape(<<unquote(seq), rest::bits>>, data, skip, unicode, acc) do
|
520
|
- string_continue(rest, data, skip 1, unicode, 0, [acc | unquote(<<char>>)])
|
521
|
- end
|
509
|
defp string_escape(<<?u, rest::bits>>, data, skip, _unicode, acc) do
|
510
|
string_escape_unicode(rest, data, skip, acc)
|
522
511
|
end
|
523
512
|
|
524
|
- defp string_escape(
|
525
|
- <<?u, seq1::binary-size(4), rest::bits>>,
|
526
|
- data,
|
527
|
- skip,
|
528
|
- _unicode,
|
529
|
- acc
|
530
|
- ) do
|
531
|
- string_escape_unicode(rest, data, skip, acc, seq1)
|
513
|
for {seq, char} <- Enum.zip(~C("\ntr/fb), ~c("\\\n\t\r/\f\b)) do
|
514
|
defp string_escape(<<unquote(seq), rest::bits>>, data, skip, unicode, acc) do
|
515
|
string_continue(rest, data, skip 1, unicode, 0, [acc | [unquote(char)]])
|
516
|
end
|
532
517
|
end
|
533
518
|
|
534
519
|
defp string_escape(_rest, _data, skip, _unicode, _acc), do: syntax_error(skip)
|
535
520
|
|
536
|
- # http://www.ietf.org/rfc/rfc2781.txt
|
537
|
- # http://perldoc.perl.org/Encode/Unicode.html#Surrogate-Pairs
|
538
|
- # http://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs
|
539
|
- defguardp is_surrogate(cp) when cp in 0xD800..0xDFFF
|
540
|
- defguardp is_surrogate_pair(hi, lo) when hi in 0xD800..0xDBFF and lo in 0xDC00..0xDFFF
|
521
|
# https://www.ietf.org/rfc/rfc2781.txt
|
522
|
# https://perldoc.perl.org/Encode::Unicode#Surrogate-Pairs
|
523
|
# https://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs
|
524
|
defguardp is_hi_surrogate(cp) when cp in 0xD800..0xDBFF
|
525
|
defguardp is_lo_surrogate(cp) when cp in 0xDC00..0xDFFF
|
541
526
|
|
542
527
|
defmacrop get_codepoint(seq, skip) do
|
543
528
|
quote bind_quoted: [seq: seq, skip: skip] do
|
|
@@ -550,30 535,45 @@ defmodule Poison.Parser do
|
550
535
|
end
|
551
536
|
end
|
552
537
|
|
553
|
- @compile {:inline, string_escape_unicode: 5}
|
538
|
@compile {:inline, string_escape_unicode: 4}
|
554
539
|
|
555
|
- defp string_escape_unicode(<<"\\u", seq2::binary-size(4), rest::bits>>, data, skip, acc, seq1) do
|
556
|
- hi = get_codepoint(seq1, skip)
|
557
|
- lo = get_codepoint(seq2, skip 6)
|
540
|
defp string_escape_unicode(<<seq1::binary-size(4), rest::bits>>, data, skip, acc) do
|
541
|
case get_codepoint(seq1, skip) do
|
542
|
hi when is_hi_surrogate(hi) ->
|
543
|
string_escape_surrogate_pair(rest, data, skip, acc, seq1, hi)
|
558
544
|
|
559
|
- cond do
|
560
|
- is_surrogate_pair(hi, lo) ->
|
561
|
- codepoint = 0x10000 ((hi &&& 0x03FF) <<< 10) (lo &&& 0x03FF)
|
562
|
- string_continue(rest, data, skip 11, true, 0, [acc, codepoint])
|
545
|
lo when is_lo_surrogate(lo) ->
|
546
|
raise ParseError, skip: skip, value: "\\u#{seq1}"
|
563
547
|
|
564
|
- is_surrogate(hi) ->
|
565
|
- raise ParseError, skip: skip, value: "\\u#{seq1}\\u#{seq2}"
|
566
|
-
|
567
|
- is_surrogate(lo) ->
|
568
|
- raise ParseError, skip: skip 6, value: "\\u#{seq2}"
|
569
|
-
|
570
|
- true ->
|
571
|
- string_continue(rest, data, skip 11, true, 0, [acc, hi, lo])
|
548
|
codepoint ->
|
549
|
string_continue(rest, data, skip 5, true, 0, [acc | [codepoint]])
|
572
550
|
end
|
573
551
|
end
|
574
552
|
|
575
|
- defp string_escape_unicode(rest, data, skip, acc, seq1) do
|
576
|
- string_continue(rest, data, skip 5, true, 0, [acc, get_codepoint(seq1, skip)])
|
553
|
defp string_escape_unicode(_rest, _data, skip, _acc), do: syntax_error(skip 1)
|
554
|
|
555
|
@compile {:inline, string_escape_surrogate_pair: 6}
|
556
|
|
557
|
defp string_escape_surrogate_pair(
|
558
|
<<"\\u", seq2::binary-size(4), rest::bits>>,
|
559
|
data,
|
560
|
skip,
|
561
|
acc,
|
562
|
seq1,
|
563
|
hi
|
564
|
) do
|
565
|
case get_codepoint(seq2, skip 6) do
|
566
|
lo when is_lo_surrogate(lo) ->
|
567
|
codepoint = 0x10000 ((hi &&& 0x03FF) <<< 10) (lo &&& 0x03FF)
|
568
|
string_continue(rest, data, skip 11, true, 0, [acc | [codepoint]])
|
569
|
|
570
|
_other ->
|
571
|
raise ParseError, skip: skip, value: "\\u#{seq1}\\u#{seq2}"
|
572
|
end
|
573
|
end
|
574
|
|
575
|
defp string_escape_surrogate_pair(_rest, _data, skip, _acc, seq1, _hi) do
|
576
|
raise ParseError, skip: skip, value: "\\u#{seq1}"
|
577
577
|
end
|
578
578
|
|
579
579
|
## Whitespace
|
changed
mix.exs
|
@@ -11,7 11,7 @@ defmodule Poison.Mixfile do
|
11
11
|
app: :poison,
|
12
12
|
name: "Poison",
|
13
13
|
version: @version,
|
14
|
- elixir: "~> 1.11",
|
14
|
elixir: "~> 1.12",
|
15
15
|
description: "An incredibly fast, pure Elixir JSON library",
|
16
16
|
source_url: "https://github.com/devinus/poison",
|
17
17
|
start_permanent: Mix.env() == :prod,
|
|
@@ -23,13 23,13 @@ defmodule Poison.Mixfile do
|
23
23
|
aliases: aliases(),
|
24
24
|
xref: [exclude: [Decimal]],
|
25
25
|
dialyzer: [
|
26
|
- ignore_warnings: ".dialyzer_ignore.exs",
|
27
26
|
plt_add_apps: [:decimal],
|
28
27
|
flags: [
|
29
28
|
:error_handling,
|
30
|
- :race_conditions,
|
31
|
- :underspecs,
|
32
|
- :unmatched_returns
|
29
|
:extra_return,
|
30
|
:missing_return,
|
31
|
:unmatched_returns,
|
32
|
:underspecs
|
33
33
|
]
|
34
34
|
],
|
35
35
|
test_coverage: [tool: ExCoveralls],
|
|
@@ -38,7 38,7 @@ defmodule Poison.Mixfile do
|
38
38
|
"coveralls.detail": :test,
|
39
39
|
"coveralls.html": :test,
|
40
40
|
"coveralls.post": :test,
|
41
|
- "coveralls.travis": :test
|
41
|
"coveralls.github": :test
|
42
42
|
]
|
43
43
|
]
|
44
44
|
end
|
|
@@ -46,18 46,16 @@ defmodule Poison.Mixfile do
|
46
46
|
# Run "mix help compile.app" to learn about applications.
|
47
47
|
def application do
|
48
48
|
# Specify extra applications you'll use from Erlang/Elixir
|
49
|
- spec = [extra_applications: []]
|
50
|
-
|
51
|
- if Mix.env() != :bench do
|
52
|
- spec
|
49
|
if Mix.env() == :bench do
|
50
|
[extra_applications: [:eex]]
|
53
51
|
else
|
54
|
- Keyword.put_new(spec, :applications, [:logger])
|
52
|
[]
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
|
- defp elixirc_paths() do
|
56
|
defp elixirc_paths do
|
59
57
|
if Mix.env() == :profile do
|
60
|
- ["lib", "profile"]
|
58
|
~w(lib profile)
|
61
59
|
else
|
62
60
|
["lib"]
|
63
61
|
end
|
|
@@ -67,19 65,22 @@ defmodule Poison.Mixfile do
|
67
65
|
defp deps do
|
68
66
|
[
|
69
67
|
{:benchee_html, "~> 1.0", only: :bench, runtime: false},
|
70
|
- {:benchee, "~> 1.0", only: :bench, runtime: false},
|
71
|
- {:credo, "~> 1.5", only: [:dev, :test], runtime: false},
|
72
|
- {:decimal, "~> 2.0", optional: true},
|
73
|
- {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
|
74
|
- {:ex_doc, "~> 0.25", only: [:dev, :test], runtime: false},
|
75
|
- {:excoveralls, "~> 0.14", only: :test, runtime: false},
|
68
|
{:benchee_markdown, "~> 0.3", only: :bench, runtime: false},
|
69
|
{:benchee, "~> 1.3", only: :bench, runtime: false},
|
70
|
{:castore, "~> 1.0", only: :test, runtime: false},
|
71
|
{:credo, "~> 1.7.7-rc", only: [:dev, :test], runtime: false},
|
72
|
{:decimal, "~> 2.1", optional: true},
|
73
|
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
|
74
|
{:ex_doc, "~> 0.34", only: [:dev, :test], runtime: false},
|
75
|
{:excoveralls, "~> 0.18", only: :test, runtime: false},
|
76
76
|
{:exjsx, "~> 4.0", only: [:bench, :profile], runtime: false},
|
77
|
- {:jason, "~> 1.2", only: [:dev, :test, :bench, :profile], runtime: false},
|
78
|
- {:jiffy, "~> 1.0", only: [:bench, :profile], runtime: false},
|
79
|
- {:json, "~> 1.4", only: [:bench, :profile], runtime: false},
|
80
|
- {:jsone, "~> 1.6", only: [:bench, :profile], runtime: false},
|
81
|
- {:junit_formatter, "~> 3.3", only: :test, runtime: false},
|
82
|
- {:stream_data, "~> 0.5", only: [:dev, :test], runtime: false},
|
77
|
{:jason, "~> 1.5.0-alpha", only: [:dev, :test, :bench, :profile], runtime: false},
|
78
|
{:jiffy, "~> 1.1", only: [:bench, :profile], runtime: false},
|
79
|
{:jsone, "~> 1.8", only: [:bench, :profile], runtime: false},
|
80
|
{:junit_formatter, "~> 3.4", only: :test, runtime: false},
|
81
|
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
|
82
|
{:stream_data, "~> 1.1", only: [:dev, :test], runtime: false},
|
83
|
{:thoas, "~> 1.2", only: [:bench, :profile], runtime: false},
|
83
84
|
{:tiny, "~> 1.0", only: [:bench, :profile], runtime: false}
|
84
85
|
]
|
85
86
|
end
|
|
@@ -88,7 89,12 @@ defmodule Poison.Mixfile do
|
88
89
|
[
|
89
90
|
main: "Poison",
|
90
91
|
canonical: "https://hexdocs.pm/poison",
|
91
|
- extras: ["README.md"]
|
92
|
extras: [
|
93
|
"README.md",
|
94
|
"CHANGELOG.md": [title: "Changelog"],
|
95
|
LICENSE: [title: "License"]
|
96
|
],
|
97
|
source_ref: "master"
|
92
98
|
]
|
93
99
|
end
|
94
100
|
|
|
@@ -104,8 110,12 @@ defmodule Poison.Mixfile do
|
104
110
|
defp aliases do
|
105
111
|
[
|
106
112
|
"deps.get": [
|
107
|
- fn _ ->
|
108
|
- System.cmd("git", ["submodule", "update", "--init"], cd: __DIR__, parallelism: true)
|
113
|
fn _args ->
|
114
|
System.cmd("git", ["submodule", "update", "--init"],
|
115
|
cd: __DIR__,
|
116
|
env: [],
|
117
|
parallelism: true
|
118
|
)
|
109
119
|
end,
|
110
120
|
"deps.get"
|
111
121
|
]
|