json(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

✨ NAME ✨

json β€” yep, that's this page, json(3) πŸ’– it's a whole library for encoding and decoding JSON, and honestly? she serves. 🫢


🌸 DESCRIPTION 🌸

okay so first thing: this module implements EEP68. πŸ“œ

And like β€” both the encoder and the decoder fully conform to RFC 8259 and ECMA 404 standards, no cap. The decoder is tested using JSONTestSuite, so the vibes are certified. πŸ§ͺ✨


πŸ¦‹ DATA TYPES πŸ¦‹

here's the whole cast of characters bestie, catch them all πŸŽ€

  • array_finish_fun() β€” the "ok we're done with this array" callback
array_finish_fun() ::
          fun((ArrayAcc :: dynamic(), OldAcc :: dynamic()) ->
                  {dynamic(), dynamic()}).
  • array_push_fun() β€” pushes one value onto the array accumulator
array_push_fun() ::
          fun((Value :: dynamic(), Acc :: dynamic()) ->
                  NewAcc :: dynamic()).
  • array_start_fun() β€” kicks off a fresh array accumulator
array_start_fun() ::
          fun((Acc :: dynamic()) -> ArrayAcc :: dynamic()).
  • continuation_state() β€” it's -opaque, so do not go poking at her insides πŸ™ˆ
-opaque continuation_state()
  • decode_value() β€” every kind of value the decoder can hand back to you
decode_value() ::
          integer() |
          float() |
          boolean() |
          null |
          binary() |
          [decode_value()] |
          #{binary() => decode_value()}.
  • decoders() β€” the callback map you feed to decode/3
decoders() ::
          #{array_start => array_start_fun(),
            array_push => array_push_fun(),
            array_finish => array_finish_fun(),
            object_start => object_start_fun(),
            object_push => object_push_fun(),
            object_finish => object_finish_fun(),
            float => from_binary_fun(),
            integer => from_binary_fun(),
            string => from_binary_fun(),
            null => term()}.
  • encode_map(Value) β€” a map that can be encoded as a JSON object, with binary(), atom() or integer() keys
encode_map(Value) :: #{binary() | atom() | integer() => Value}.
  • encode_value() β€” a simple JSON value encodeable with json:encode/1
encode_value() ::
          integer() |
          float() |
          boolean() |
          null |
          binary() |
          atom() |
          [encode_value()] |
          encode_map(encode_value()).
  • encoder() β€” the encode callback shape
encoder() :: fun((dynamic(), encoder()) -> iodata()).
  • formatter() β€” the formatter callback shape
formatter() ::
          fun((Term :: dynamic(),
               Encoder :: formatter(),
               State :: map()) ->
              iodata()).
  • from_binary_fun() β€” takes a binary(), gives you back dynamic()
from_binary_fun() :: fun((binary()) -> dynamic()).
  • object_finish_fun() β€” the "ok we're done with this object" callback
object_finish_fun() ::
          fun((ObjectAcc :: dynamic(), OldAcc :: dynamic()) ->
                  {dynamic(), dynamic()}).
  • object_push_fun() β€” pushes a key + value pair onto the object accumulator
object_push_fun() ::
          fun((Key :: dynamic(), Value :: dynamic(), Acc :: dynamic()) ->
                  NewAcc :: dynamic()).
  • object_start_fun() β€” kicks off a fresh object accumulator
object_start_fun() ::
          fun((Acc :: dynamic()) -> ObjectAcc :: dynamic()).

πŸ’… FUNCTIONS πŸ’…

🌟 decode/1 🌟

decode(binary()) -> decode_value().

Reads one JSON value out of Binary. 🫢

The basic data mapping it's working with is this:

  • JSON Number β†’ Erlang integer(), float()
  • JSON Boolean β†’ Erlang true, false
  • JSON Null β†’ Erlang null
  • JSON String β†’ Erlang binary()
  • JSON Object β†’ Erlang #{binary() => _}

😭 Errors 😭

  • error(unexpected_end) if Binary contains incomplete JSON value
  • error({invalid_byte, Byte}) if Binary contains unexpected byte or invalid UTF-8 byte
  • error({unexpected_sequence, Bytes}) if Binary contains invalid UTF-8 escape

✨ Example ✨

> json:decode(<<"{\"foo\": 1}">>).
#{<<"foo">> => 1}

🌟 decode/3 🌟

decode(binary(), dynamic(), decoders()) ->
                {Result :: dynamic(), Acc :: dynamic(), binary()}.

Parses a JSON value from Binary, same energy as decode/1, except you get to customize the whole decoding process with the callbacks you specify in Decoders. Those callbacks use the Acc value as their initial accumulator.

And any leftover, unparsed data in Binary will be returned to you too. 🎁

πŸŽ€ Default callbacks πŸŽ€

All callbacks are optional. If you don't provide them, they fall back to the implementations used by the decode/1 function:

  • for array_start: fun(_) -> [] end
  • for array_push: fun(Elem, Acc) -> [Elem | Acc] end
  • for array_finish: fun(Acc, OldAcc) -> {lists:reverse(Acc), OldAcc} end
  • for object_start: fun(_) -> [] end
  • for object_push: fun(Key, Value, Acc) -> [{Key, Value} | Acc] end
  • for object_finish: fun(Acc, OldAcc) -> {maps:from_list(Acc), OldAcc} end
  • for float: fun erlang:binary_to_float/1
  • for integer: fun erlang:binary_to_integer/1
  • for string: fun (Value) -> Value end
  • for null: the atom null

😭 Errors 😭

  • error({invalid_byte, Byte}) if Binary contains unexpected byte or invalid UTF-8 byte
  • error({unexpected_sequence, Bytes}) if Binary contains invalid UTF-8 escape
  • error(unexpected_end) if Binary contains incomplete JSON value

✨ Example ✨

Decoding object keys as atoms:

> Push = fun(Key, Value, Acc) -> [{binary_to_existing_atom(Key), Value} | Acc] end.
> json:decode(<<"{\"foo\": 1}">>, ok, #{object_push => Push}).
{#{foo => 1},ok,<<>>}

🌟 decode_continue/2 🌟

decode_continue(binary() | end_of_input,
                State :: continuation_state()) ->
                   {Result :: dynamic(),
                    Acc :: dynamic(),
                    binary()} |
                   {continue, continuation_state()}.

Continue parsing a stream of bytes of a JSON value. 🌊

Similar to decode_start/3, if the function returns {continue, State} and there is no more data, use end_of_input instead of a binary.

> {continue, State} = json:decode_start(<<"{\"foo\":">>, ok, #{}).
> json:decode_continue(<<"1}">>, State).
{#{foo => 1},ok,<<>>}

> {continue, State} = json:decode_start(<<"123">>, ok, #{}).
> json:decode_continue(end_of_input, State).
{123,ok,<<>>}

🌟 decode_start/3 🌟

decode_start(binary(), dynamic(), decoders()) ->
                {Result :: dynamic(), Acc :: dynamic(), binary()} |
                {continue, continuation_state()}.

Begin parsing a stream of bytes of a JSON value. πŸš€

Similar to decode/3, but it returns as soon as a complete JSON value can be parsed β€” or it returns {continue, State} for incomplete data, and that State can be fed to the decode_continue/2 function when more data shows up.


🌟 encode/1 🌟

encode(encode_value()) -> iodata().

Generates JSON corresponding to Term. πŸ’–

The basic data mapping it's working with:

  • Erlang integer(), float() β†’ JSON Number
  • Erlang true, false β†’ JSON Boolean
  • Erlang null β†’ JSON Null
  • Erlang binary() β†’ JSON String
  • Erlang atom() β†’ JSON String
  • Erlang list() β†’ JSON Array
  • Erlang #{binary() => _} β†’ JSON Object
  • Erlang #{atom() => _} β†’ JSON Object
  • Erlang #{integer() => _} β†’ JSON Object

This is equivalent to encode(Term, fun json:encode_value/2).

✨ Examples ✨

> iolist_to_binary(json:encode(#{foo => <<"bar">>})).
<<"{\"foo\":\"bar\"}">>

🌟 encode/2 🌟

encode(dynamic(), encoder()) -> iodata().

Generates JSON corresponding to Term, and you can customize it with the Encoder callback. That callback will be recursively called for all the data to be encoded and is expected to return the corresponding encoded JSON as iodata.

Various encode_* functions in this module can be used to help in constructing such callbacks. πŸ› οΈ

✨ Examples ✨

An encoder that uses a heuristic to differentiate object-like lists of key-value pairs from plain lists:

> encoder([{_, _} | _] = Value, Encode) -> json:encode_key_value_list(Value, Encode);
> encoder(Other, Encode) -> json:encode_value(Other, Encode).
> custom_encode(Value) -> json:encode(Value, fun(Value, Encode) -> encoder(Value, Encode) end).
> iolist_to_binary(custom_encode([{a, []}, {b, 1}])).
<<"{\"a\":[],\"b\":1}">>

🌟 encode_atom/2 🌟

encode_atom(atom(), encoder()) -> iodata().

The default encoder for atoms, used by json:encode/1. 🌈

It encodes the atom null as JSON null, the atoms true and false as JSON booleans, and everything else as JSON strings β€” calling the Encode callback with the corresponding binary.


🌟 encode_binary/1 🌟

encode_binary(binary()) -> iodata().

The default encoder for binaries as JSON strings, used by json:encode/1. πŸŽ€

😭 Errors 😭

  • error(unexpected_end) if the binary contains incomplete UTF-8 sequences.
  • error({invalid_byte, Byte}) if the binary contains invalid UTF-8 sequences.

🌟 encode_binary_escape_all/1 🌟

encode_binary_escape_all(binary()) -> iodata().

Encoder for binaries as JSON strings that produces pure-ASCII JSON. ✨

For any non-ASCII unicode character, a corresponding \\uXXXX sequence is used.

😭 Errors 😭

  • error(unexpected_end) if the binary contains incomplete UTF-8 sequences.
  • error({invalid_byte, Byte}) if the binary contains invalid UTF-8 sequences.

🌟 encode_float/1 🌟

encode_float(float()) -> iodata().

The default encoder for floats as JSON numbers, used by json:encode/1. πŸ”₯


🌟 encode_integer/1 🌟

encode_integer(integer()) -> iodata().

Default encoder for integers as JSON numbers used by json:encode/1. πŸ’–


  • encode_key_value_list([{term(), term()}], encoder()) -> iodata(). β€” okay so this is the encoder for lists of key-value pairs as JSON objects. It accepts lists with atom, binary, integer, or float keys. Slay. ✨
  • encode_key_value_list_checked([{term(), term()}], encoder()) -> iodata(). β€” same encoder for lists of key-value pairs as JSON objects. Accepts lists with atom, binary, integer, or float keys. BUT it verifies that no duplicate keys will be produced in the resulting JSON object. Highkey important. πŸ‘‘

Errors

Raises error({duplicate_key, Key}) if there are duplicates. πŸ’€

  • encode_list(list(), encoder()) -> iodata(). β€” default encoder for lists as JSON arrays used by json:encode/1. It's giving array energy. 🌈
  • encode_map(encode_map(dynamic()), encoder()) -> iodata(). β€” default encoder for maps as JSON objects used by json:encode/1. Accepts maps with atom, binary, integer, or float keys. Main character map vibes. πŸ—ΊοΈ
  • encode_map_checked(map(), encoder()) -> iodata(). β€” encoder for maps as JSON objects. Accepts maps with atom, binary, integer, or float keys. Verifies that no duplicate keys will be produced in the resulting JSON object. The way it checks... obsessed. πŸ•΅οΈ

Errors

Raises error({duplicate_key, Key}) if there are duplicates.

  • encode_value(dynamic(), encoder()) -> iodata(). β€” default encoder used by json:encode/1. Recursively calls Encode on all the values in Value. It goes deep, bestie. πŸ”
  • format(Term :: encode_value()) -> iodata(). β€” generates formatted JSON corresponding to Term. Similar to encode/1 but with added whitespaces for formatting. So pretty. ✨
> io:put_chars(json:format(#{foo => <<"bar">>, baz => 52})).
{
  "baz": 52,
  "foo": "bar"
}
ok
  • format(Term :: encode_value(), Opts :: map()) -> iodata(); (Term :: dynamic(), Encoder :: formatter()) -> iodata(). β€” generates formatted JSON corresponding to Term. Equivalent to format(Term, fun json:format_value/3, Options) or format(Term, Encoder, #{}). Two paths, one slay. πŸ’…
  • format(Term :: dynamic(), Encoder :: formatter(), Options :: map()) -> iodata(). β€” generates formatted JSON corresponding to Term. Similar to encode/2, can be customised with the Encoder callback and Options. Options can include 'indent' to specify number of spaces per level and 'max' which loosely limits the width of lists. The Encoder will get a 'State' argument which contains the 'Options' maps merged with other data when recursing through 'Term'. format_value/3 or various encode_* functions in this module can be used to help in constructing such callbacks. This is the customisation station. πŸŽ€
> formatter({posix_time, SysTimeSecs}, Encode, State) ->
    TimeStr = calendar:system_time_to_rfc3339(SysTimeSecs, [{offset, "Z"}]),
    json:format_value(unicode:characters_to_binary(TimeStr), Encode, State);
> formatter(Other, Encode, State) -> json:format_value(Other, Encode, State).
>
> Fun = fun(Value, Encode, State) -> formatter(Value, Encode, State) end.
> Options = #{indent => 4}.
> Term = #{id => 1, time => {posix_time, erlang:system_time(seconds)}}.
>
> io:put_chars(json:format(Term, Fun, Options)).
{
    "id": 1,
    "time": "2024-05-23T16:07:48Z"
}
ok
  • format_key_value_list([{term(), term()}], Encode :: formatter(), State :: map()) -> iodata(). β€” format function for lists of key-value pairs as JSON objects. Accepts lists with atom, binary, integer, or float keys. πŸ’–
  • format_key_value_list_checked([{term(), term()}], Encoder :: formatter(), State :: map()) -> iodata(). β€” format function for lists of key-value pairs as JSON objects. Accepts lists with atom, binary, integer, or float keys. Verifies that no duplicate keys will be produced in the resulting JSON object. No dupes allowed. 🚫

Errors

Raises error({duplicate_key, Key}) if there are duplicates.

  • format_value(Value :: dynamic(), Encode :: formatter(), State :: map()) -> iodata(). β€” default format function used by json:format/1. Recursively calls Encode on all the values in Value, and indents objects and lists. It's the recursive queen. πŸ‘‘

That's the whole vibe, bestie. This page is brought to you by Ericsson AB, stdlib 8.0.4, and json(3). No date on this footer, but the version is iconic. Periodt. πŸ’…βœ¨

πŸ“¦ where this page comes from