Skip to content

Commit 345a880

Browse files
committed
Add JSON.DecodeError
1 parent 3002c94 commit 345a880

File tree

2 files changed

+44
-26
lines changed

2 files changed

+44
-26
lines changed

lib/elixir/lib/json.ex

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ defprotocol JSON.Encoder do
1313
1414
@derive JSON.Encoder
1515
defstruct ...
16-
16+
1717
> #### Leaking Private Information {: .error}
1818
>
1919
> The `:except` approach should be used carefully to avoid
@@ -150,6 +150,13 @@ defimpl JSON.Encoder, for: Map do
150150
end
151151
end
152152

153+
defmodule JSON.DecodeError do
154+
@moduledoc """
155+
The exception raised by `JSON.decode!/1`.
156+
"""
157+
defexception [:message, :offset, :data]
158+
end
159+
153160
defmodule JSON do
154161
@moduledoc ~S"""
155162
JSON encoding and decoding.
@@ -192,7 +199,7 @@ defmodule JSON do
192199

193200
@moduledoc since: "1.18.0"
194201

195-
@type decode_error ::
202+
@type decode_error_reason ::
196203
{:unexpected_end, non_neg_integer()}
197204
| {:invalid_byte, non_neg_integer(), byte()}
198205
| {:unexpected_sequence, non_neg_integer(), binary()}
@@ -211,11 +218,11 @@ defmodule JSON do
211218
212219
The error tuple will have one of the following reasons.
213220
214-
* `{:unexpected_end, position}` if `binary` contains incomplete JSON value
215-
* `{:invalid_byte, position, byte}` if `binary` contains unexpected byte or invalid UTF-8 byte
216-
* `{:unexpected_sequence, position, bytes}` if `binary` contains invalid UTF-8 escape
221+
* `{:unexpected_end, offset}` if `binary` contains incomplete JSON value
222+
* `{:invalid_byte, offset, byte}` if `binary` contains unexpected byte or invalid UTF-8 byte
223+
* `{:unexpected_sequence, offset, bytes}` if `binary` contains invalid UTF-8 escape
217224
"""
218-
@spec decode(binary()) :: {:ok, term()} | {:error, decode_error()}
225+
@spec decode(binary()) :: {:ok, term()} | {:error, decode_error_reason()}
219226
def decode(binary) when is_binary(binary) do
220227
with {decoded, :ok, rest} <- decode(binary, :ok, []) do
221228
if rest == "" do
@@ -250,7 +257,7 @@ defmodule JSON do
250257
251258
For streaming decoding, see Erlang's `:json` module.
252259
"""
253-
@spec decode(binary(), term(), keyword()) :: {term(), term(), binary()} | decode_error()
260+
@spec decode(binary(), term(), keyword()) :: {term(), term(), binary()} | decode_error_reason()
254261
def decode(binary, acc, decoders) when is_binary(binary) and is_list(decoders) do
255262
decoders = Keyword.put_new(decoders, :null, nil)
256263

@@ -261,14 +268,14 @@ defmodule JSON do
261268
{:error, {:unexpected_end, byte_size(binary)}}
262269

263270
:error, {:invalid_byte, byte} ->
264-
{:error, {:invalid_byte, position(__STACKTRACE__), byte}}
271+
{:error, {:invalid_byte, offset(__STACKTRACE__), byte}}
265272

266273
:error, {:unexpected_sequence, bytes} ->
267-
{:error, {:unexpected_sequence, position(__STACKTRACE__), bytes}}
274+
{:error, {:unexpected_sequence, offset(__STACKTRACE__), bytes}}
268275
end
269276
end
270277

271-
defp position(stacktrace) do
278+
defp offset(stacktrace) do
272279
with [{_, _, _, opts} | _] <- stacktrace,
273280
%{cause: %{position: position}} <- opts[:error_info] do
274281
position
@@ -293,14 +300,23 @@ defmodule JSON do
293300
{:ok, decoded} ->
294301
decoded
295302

296-
{:error, {:unexpected_end, position}} ->
297-
raise ArgumentError, "unexpected end of JSON binary at position #{position}"
298-
299-
{:error, {:invalid_byte, position, byte}} ->
300-
raise ArgumentError, "invalid byte #{byte} at position #{position}"
301-
302-
{:error, {:unexpected_sequence, position, bytes}} ->
303-
raise ArgumentError, "unexpected sequence #{inspect(bytes)} at position #{position}"
303+
{:error, {:unexpected_end, offset}} ->
304+
raise JSON.DecodeError,
305+
message: "unexpected end of JSON binary at position (byte offset) #{offset}",
306+
data: binary,
307+
offset: offset
308+
309+
{:error, {:invalid_byte, offset, byte}} ->
310+
raise JSON.DecodeError,
311+
message: "invalid byte #{byte} at position (byte offset) #{offset}",
312+
data: binary,
313+
offset: offset
314+
315+
{:error, {:unexpected_sequence, offset, bytes}} ->
316+
raise JSON.DecodeError,
317+
message: "unexpected sequence #{inspect(bytes)} at position (byte offset) #{offset}",
318+
data: binary,
319+
offset: offset
304320
end
305321
end
306322

@@ -316,6 +332,7 @@ defmodule JSON do
316332
"[123,\"string\",{\"key\":\"value\"}]"
317333
318334
"""
335+
@spec encode!(a, (a -> iodata())) :: binary() when a: var
319336
def encode!(term, encoder \\ &encode_value/2) do
320337
IO.iodata_to_binary(encoder.(term, encoder))
321338
end
@@ -336,6 +353,7 @@ defmodule JSON do
336353
"[123,\"string\",{\"key\":\"value\"}]"
337354
338355
"""
356+
@spec encode!(a, (a -> iodata())) :: iodata() when a: var
339357
def encode_to_iodata!(term, encoder \\ &encode_value/2) do
340358
encoder.(term, encoder)
341359
end

lib/elixir/test/elixir/json_test.exs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,26 +148,26 @@ defmodule JSONTest do
148148
test "unexpected end" do
149149
assert JSON.decode("{") == {:error, {:unexpected_end, 1}}
150150

151-
assert_raise ArgumentError, "unexpected end of JSON binary at position 1", fn ->
152-
JSON.decode!("{")
153-
end
151+
assert_raise JSON.DecodeError,
152+
"unexpected end of JSON binary at position (byte offset) 1",
153+
fn -> JSON.decode!("{") end
154154
end
155155

156156
test "invalid byte" do
157157
assert JSON.decode(",") == {:error, {:invalid_byte, 0, ?,}}
158158
assert JSON.decode("123o") == {:error, {:invalid_byte, 3, ?o}}
159159

160-
assert_raise ArgumentError, "invalid byte 111 at position 3", fn ->
161-
JSON.decode!("123o")
162-
end
160+
assert_raise JSON.DecodeError,
161+
"invalid byte 111 at position (byte offset) 3",
162+
fn -> JSON.decode!("123o") end
163163
end
164164

165165
test "unexpected sequence" do
166166
assert JSON.decode("\"\\ud8aa\\udcxx\"") ==
167167
{:error, {:unexpected_sequence, 1, "\\ud8aa\\udcxx"}}
168168

169-
assert_raise ArgumentError,
170-
"unexpected sequence \"\\\\ud8aa\\\\udcxx\" at position 1",
169+
assert_raise JSON.DecodeError,
170+
"unexpected sequence \"\\\\ud8aa\\\\udcxx\" at position (byte offset) 1",
171171
fn -> JSON.decode!("\"\\ud8aa\\udcxx\"") end
172172
end
173173
end

0 commit comments

Comments
 (0)