@@ -187,6 +187,117 @@ defmodule JSON do
187187
188188 @ moduledoc since: "1.18.0"
189189
190+ @ type decode_error ::
191+ { :unexpected_end , non_neg_integer ( ) }
192+ | { :invalid_byte , non_neg_integer ( ) , byte ( ) }
193+ | { :unexpected_sequence , non_neg_integer ( ) , binary ( ) }
194+
195+ @ doc ~S"""
196+ Decodes the given JSON.
197+
198+ Returns `{:ok, decoded}` or `{:error, reason}`.
199+
200+ ## Examples
201+
202+ iex> JSON.decode("[null,123,\" string\" ,{\" key\" :\" value\" }]")
203+ {:ok, [nil, 123, "string", %{"key" => "value"}]}
204+
205+ ## Error reasons
206+
207+ The error tuple will have one of the following reasons.
208+
209+ * `{:unexpected_end, position}` if `binary` contains incomplete JSON value
210+ * `{:invalid_byte, position, byte}` if `binary` contains unexpected byte or invalid UTF-8 byte
211+ * `{:unexpected_sequence, position, bytes}` if `binary` contains invalid UTF-8 escape
212+ """
213+ @ spec decode ( binary ( ) ) :: { :ok , term ( ) } | decode_error ( )
214+ def decode ( binary ) when is_binary ( binary ) do
215+ with { decoded , :ok , rest } <- decode ( binary , :ok , [ ] ) do
216+ if rest == "" do
217+ { :ok , decoded }
218+ else
219+ { :error , { :invalid_byte , byte_size ( binary ) - byte_size ( rest ) , :binary . at ( rest , 0 ) } }
220+ end
221+ end
222+ end
223+
224+ @ doc ~S"""
225+ Decodes the given JSON with the given decoders.
226+
227+ Returns `{decoded, acc, rest}` or `{:error, reason}`.
228+ See `decode/1` for the error reasons.
229+
230+ ## Decoders
231+
232+ All decoders are optional. If not provided, they will fall back to
233+ implementations used by the `decode/1` function:
234+
235+ * for `array_start`: `fn _ -> [] end`
236+ * for `array_push`: `fn elem, acc -> [elem | acc] end`
237+ * for `array_finish`: `fn acc, old_acc -> {Enum.reverse(acc), old_acc} end`
238+ * for `object_start`: `fn _ -> [] end`
239+ * for `object_push`: `fn key, value, acc -> [{key, value} | acc] end`
240+ * for `object_finish`: `fn acc, old_acc -> {Map.new(acc), old_acc} end`
241+ * for `float`: `&String.to_float/1`
242+ * for `integer`: `&String.to_integer/1`
243+ * for `string`: `&Function.identity/1`
244+ * for `null`: the atom `nil`
245+
246+ For streaming decoding, see Erlang's `:json` module.
247+ """
248+ @ spec decode ( binary ( ) , term ( ) , keyword ( ) ) :: { term ( ) , term ( ) , binary ( ) } | decode_error ( )
249+ def decode ( binary , acc , decoders ) when is_binary ( binary ) and is_list ( decoders ) do
250+ decoders = Keyword . put_new ( decoders , :null , nil )
251+
252+ try do
253+ :elixir_json . decode ( binary , acc , Map . new ( decoders ) )
254+ catch
255+ :error , :unexpected_end ->
256+ { :error , { :unexpected_end , byte_size ( binary ) } }
257+
258+ :error , { :invalid_byte , byte } ->
259+ { :error , { :invalid_byte , position ( __STACKTRACE__ ) , byte } }
260+
261+ :error , { :unexpected_sequence , bytes } ->
262+ { :error , { :unexpected_sequence , position ( __STACKTRACE__ ) , bytes } }
263+ end
264+ end
265+
266+ defp position ( stacktrace ) do
267+ with [ { _ , _ , _ , opts } | _ ] <- stacktrace ,
268+ % { cause: % { position: position } } <- opts [ :error_info ] do
269+ position
270+ else
271+ _ -> 0
272+ end
273+ end
274+
275+ @ doc ~S"""
276+ Decodes the given JSON but raises an exception in case of errors.
277+
278+ Returns the decoded content. See `decode!/1` for possible errors.
279+
280+ ## Examples
281+
282+ iex> JSON.decode!("[null,123,\" string\" ,{\" key\" :\" value\" }]")
283+ [nil, 123, "string", %{"key" => "value"}]
284+ """
285+ def decode! ( binary ) when is_binary ( binary ) do
286+ case decode ( binary ) do
287+ { :ok , decoded } ->
288+ decoded
289+
290+ { :error , { :unexpected_end , position } } ->
291+ raise ArgumentError , "unexpected end of JSON binary at position #{ position } "
292+
293+ { :error , { :invalid_byte , position , byte } } ->
294+ raise ArgumentError , "invalid byte #{ byte } at position #{ position } "
295+
296+ { :error , { :unexpected_sequence , position , bytes } } ->
297+ raise ArgumentError , "unexpected sequence #{ inspect ( bytes ) } at position #{ position } "
298+ end
299+ end
300+
190301 @ doc ~S"""
191302 Encodes the given term to JSON as a binary.
192303
0 commit comments