-module(gleamson).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleamson.gleam").
-export([parse_bits/1, parse/1, to_string_tree/1, to_string/1, array/2, nullable/2, from_dict/3, field/2, get/2, index/2, to_dict/1, as_string/1, as_int/1, as_float/1, as_bool/1, to_string_pretty_with/2, to_string_pretty/1, merge/2, semantically_equal/2, pointer/2]).
-export_type([json/0, parse_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" gleamson — a pure-Gleam JSON library.\n"
"\n"
" Unlike libraries that delegate to a platform's native JSON facilities,\n"
" `gleamson` is written entirely in Gleam. That means:\n"
"\n"
" * It runs identically on the Erlang and JavaScript targets.\n"
" * It has no Erlang/OTP version requirement.\n"
" * Parse errors carry a precise byte position, on every runtime.\n"
" * The `Json` value is a transparent type you can pattern match on,\n"
" transform, and build directly — no opaque box.\n"
"\n"
" Parsing is a single pass over a `BitArray` using Gleam's bit-array\n"
" pattern matching, which compiles to fast binary matching on the BEAM.\n"
).
-type json() :: null |
{bool, boolean()} |
{int, integer()} |
{float, float()} |
{string, binary()} |
{array, list(json())} |
{object, list({binary(), json()})}.
-type parse_error() :: unexpected_end |
{unexpected_byte, binary(), integer()} |
{unexpected_token, binary(), integer()}.
-file("src/gleamson.gleam", 374).
-spec position(bitstring(), integer()) -> integer().
position(Bits, Len) ->
Len - erlang:byte_size(Bits).
-file("src/gleamson.gleam", 424).
-spec hex_char(integer()) -> binary().
hex_char(Digit) ->
case Digit of
10 ->
<<"a"/utf8>>;
11 ->
<<"b"/utf8>>;
12 ->
<<"c"/utf8>>;
13 ->
<<"d"/utf8>>;
14 ->
<<"e"/utf8>>;
15 ->
<<"f"/utf8>>;
_ ->
erlang:integer_to_binary(Digit)
end.
-file("src/gleamson.gleam", 417).
-spec to_hex_loop(integer(), binary()) -> binary().
to_hex_loop(N, Acc) ->
case N of
0 ->
Acc;
_ ->
to_hex_loop(N div 16, <<(hex_char(N rem 16))/binary, Acc/binary>>)
end.
-file("src/gleamson.gleam", 410).
-spec to_hex(integer()) -> binary().
to_hex(N) ->
case N of
0 ->
<<"0"/utf8>>;
_ ->
to_hex_loop(N, <<""/utf8>>)
end.
-file("src/gleamson.gleam", 406).
-spec byte_hex(integer()) -> binary().
byte_hex(Byte) ->
<<"0x"/utf8, (string:uppercase(to_hex(Byte)))/binary>>.
-file("src/gleamson.gleam", 395).
-spec describe_byte(integer()) -> binary().
describe_byte(Byte) ->
case (Byte >= 16#20) andalso (Byte =< 16#7E) of
true ->
case gleam@string:utf_codepoint(Byte) of
{ok, Cp} ->
gleam_stdlib:utf_codepoint_list_to_string([Cp]);
{error, _} ->
byte_hex(Byte)
end;
false ->
byte_hex(Byte)
end.
-file("src/gleamson.gleam", 364).
-spec skip_whitespace(bitstring()) -> bitstring().
skip_whitespace(Bits) ->
case Bits of
<<16#20, Rest/bitstring>> ->
skip_whitespace(Rest);
<<16#09, Rest@1/bitstring>> ->
skip_whitespace(Rest@1);
<<16#0A, Rest@2/bitstring>> ->
skip_whitespace(Rest@2);
<<16#0D, Rest@3/bitstring>> ->
skip_whitespace(Rest@3);
_ ->
Bits
end.
-file("src/gleamson.gleam", 348).
?DOC(
" `float.parse` is backed by Erlang's `binary_to_float`, which insists on a\n"
" decimal point. JSON allows `1e9`, so we make sure the mantissa has one and\n"
" normalise the exponent letter to lowercase. The result is then parsed\n"
" identically on both targets.\n"
).
-spec normalize_float(binary()) -> binary().
normalize_float(Lexeme) ->
Lexeme@1 = gleam@string:replace(Lexeme, <<"E"/utf8>>, <<"e"/utf8>>),
case gleam@string:split_once(Lexeme@1, <<"e"/utf8>>) of
{ok, {Mantissa, Exponent}} ->
Mantissa@1 = case gleam_stdlib:contains_string(
Mantissa,
<<"."/utf8>>
) of
true ->
Mantissa;
false ->
<<Mantissa/binary, ".0"/utf8>>
end,
<<<<Mantissa@1/binary, "e"/utf8>>/binary, Exponent/binary>>;
{error, _} ->
Lexeme@1
end.
-file("src/gleamson.gleam", 333).
-spec is_integer_lexeme(binary()) -> boolean().
is_integer_lexeme(Lexeme) ->
case {gleam_stdlib:contains_string(Lexeme, <<"."/utf8>>),
gleam_stdlib:contains_string(Lexeme, <<"e"/utf8>>),
gleam_stdlib:contains_string(Lexeme, <<"E"/utf8>>)} of
{false, false, false} ->
true;
{_, _, _} ->
false
end.
-file("src/gleamson.gleam", 324).
-spec is_number_char(integer()) -> boolean().
is_number_char(Byte) ->
((((((Byte >= 16#30) andalso (Byte =< 16#39)) orelse (Byte =:= 16#2D))
orelse (Byte =:= 16#2B))
orelse (Byte =:= 16#2E))
orelse (Byte =:= 16#65))
orelse (Byte =:= 16#45).
-file("src/gleamson.gleam", 313).
-spec take_number(bitstring(), bitstring()) -> {bitstring(), bitstring()}.
take_number(Bits, Acc) ->
case Bits of
<<Byte, Rest/bitstring>> ->
case is_number_char(Byte) of
true ->
take_number(Rest, <<Acc/bitstring, Byte>>);
false ->
{Acc, Bits}
end;
_ ->
{Acc, Bits}
end.
-file("src/gleamson.gleam", 293).
-spec parse_number(bitstring(), integer()) -> {ok, {json(), bitstring()}} |
{error, parse_error()}.
parse_number(Bits, Len) ->
{Lexeme_bits, Rest} = take_number(Bits, <<>>),
case gleam@bit_array:to_string(Lexeme_bits) of
{ok, Lexeme} ->
case is_integer_lexeme(Lexeme) of
true ->
case gleam_stdlib:parse_int(Lexeme) of
{ok, Value} ->
{ok, {{int, Value}, Rest}};
{error, _} ->
{error,
{unexpected_token, Lexeme, position(Bits, Len)}}
end;
false ->
case gleam_stdlib:parse_float(normalize_float(Lexeme)) of
{ok, Value@1} ->
{ok, {{float, Value@1}, Rest}};
{error, _} ->
{error,
{unexpected_token, Lexeme, position(Bits, Len)}}
end
end;
{error, _} ->
{error,
{unexpected_byte, <<"invalid UTF-8"/utf8>>, position(Bits, Len)}}
end.
-file("src/gleamson.gleam", 386).
-spec hex_digit(integer()) -> {ok, integer()} | {error, nil}.
hex_digit(Byte) ->
case Byte of
_ when (Byte >= 16#30) andalso (Byte =< 16#39) ->
{ok, Byte - 16#30};
_ when (Byte >= 16#41) andalso (Byte =< 16#46) ->
{ok, (Byte - 16#41) + 10};
_ when (Byte >= 16#61) andalso (Byte =< 16#66) ->
{ok, (Byte - 16#61) + 10};
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 378).
-spec hex4(integer(), integer(), integer(), integer()) -> {ok, integer()} |
{error, nil}.
hex4(A, B, C, D) ->
gleam@result:'try'(
hex_digit(A),
fun(A@1) ->
gleam@result:'try'(
hex_digit(B),
fun(B@1) ->
gleam@result:'try'(
hex_digit(C),
fun(C@1) ->
gleam@result:'try'(
hex_digit(D),
fun(D@1) ->
{ok,
(((((A@1 * 16) + B@1) * 16) + C@1) * 16)
+ D@1}
end
)
end
)
end
)
end
).
-file("src/gleamson.gleam", 278).
-spec append_codepoint(integer(), bitstring(), integer(), bitstring()) -> {ok,
{binary(), bitstring()}} |
{error, parse_error()}.
append_codepoint(Code, Rest, Len, Acc) ->
case gleam@string:utf_codepoint(Code) of
{ok, Cp} ->
parse_string_loop(Rest, Len, <<Acc/bitstring, Cp/utf8>>);
{error, _} ->
{error,
{unexpected_byte,
<<"invalid code point"/utf8>>,
position(Rest, Len)}}
end.
-file("src/gleamson.gleam", 252).
-spec parse_low_surrogate(bitstring(), integer(), bitstring(), integer()) -> {ok,
{binary(), bitstring()}} |
{error, parse_error()}.
parse_low_surrogate(Bits, Len, Acc, High) ->
case Bits of
<<"\\u"/utf8, A, B, C, D, Rest/bitstring>> ->
case hex4(A, B, C, D) of
{ok, Low} ->
case (Low >= 16#DC00) andalso (Low =< 16#DFFF) of
true ->
Code = (16#10000 + ((High - 16#D800) * 16#400)) + (Low
- 16#DC00),
append_codepoint(Code, Rest, Len, Acc);
false ->
{error,
{unexpected_byte,
<<"invalid low surrogate"/utf8>>,
position(Bits, Len)}}
end;
{error, _} ->
{error,
{unexpected_byte,
<<"invalid \\u escape"/utf8>>,
position(Bits, Len)}}
end;
_ ->
{error,
{unexpected_byte,
<<"unpaired surrogate"/utf8>>,
position(Bits, Len)}}
end.
-file("src/gleamson.gleam", 232).
-spec parse_unicode_escape(bitstring(), integer(), bitstring()) -> {ok,
{binary(), bitstring()}} |
{error, parse_error()}.
parse_unicode_escape(Bits, Len, Acc) ->
case Bits of
<<A, B, C, D, Rest/bitstring>> ->
case hex4(A, B, C, D) of
{ok, Code} ->
case (Code >= 16#D800) andalso (Code =< 16#DBFF) of
true ->
parse_low_surrogate(Rest, Len, Acc, Code);
false ->
append_codepoint(Code, Rest, Len, Acc)
end;
{error, _} ->
{error,
{unexpected_byte,
<<"invalid \\u escape"/utf8>>,
position(Bits, Len)}}
end;
_ ->
{error, unexpected_end}
end.
-file("src/gleamson.gleam", 215).
-spec parse_escape(bitstring(), integer(), bitstring()) -> {ok,
{binary(), bitstring()}} |
{error, parse_error()}.
parse_escape(Bits, Len, Acc) ->
case Bits of
<<"\""/utf8, Rest/bitstring>> ->
parse_string_loop(Rest, Len, <<Acc/bitstring, 16#22>>);
<<"\\"/utf8, Rest@1/bitstring>> ->
parse_string_loop(Rest@1, Len, <<Acc/bitstring, 16#5C>>);
<<"/"/utf8, Rest@2/bitstring>> ->
parse_string_loop(Rest@2, Len, <<Acc/bitstring, 16#2F>>);
<<"b"/utf8, Rest@3/bitstring>> ->
parse_string_loop(Rest@3, Len, <<Acc/bitstring, 16#08>>);
<<"f"/utf8, Rest@4/bitstring>> ->
parse_string_loop(Rest@4, Len, <<Acc/bitstring, 16#0C>>);
<<"n"/utf8, Rest@5/bitstring>> ->
parse_string_loop(Rest@5, Len, <<Acc/bitstring, 16#0A>>);
<<"r"/utf8, Rest@6/bitstring>> ->
parse_string_loop(Rest@6, Len, <<Acc/bitstring, 16#0D>>);
<<"t"/utf8, Rest@7/bitstring>> ->
parse_string_loop(Rest@7, Len, <<Acc/bitstring, 16#09>>);
<<"u"/utf8, Rest@8/bitstring>> ->
parse_unicode_escape(Rest@8, Len, Acc);
<<Byte, _/bitstring>> ->
{error, {unexpected_byte, describe_byte(Byte), position(Bits, Len)}};
_ ->
{error, unexpected_end}
end.
-file("src/gleamson.gleam", 196).
-spec parse_string_loop(bitstring(), integer(), bitstring()) -> {ok,
{binary(), bitstring()}} |
{error, parse_error()}.
parse_string_loop(Bits, Len, Acc) ->
case Bits of
<<"\""/utf8, Rest/bitstring>> ->
case gleam@bit_array:to_string(Acc) of
{ok, Value} ->
{ok, {Value, Rest}};
{error, _} ->
{error,
{unexpected_byte,
<<"invalid UTF-8"/utf8>>,
position(Bits, Len)}}
end;
<<"\\"/utf8, Rest@1/bitstring>> ->
parse_escape(Rest@1, Len, Acc);
<<Byte, Rest@2/bitstring>> ->
case Byte < 16#20 of
true ->
{error,
{unexpected_byte,
describe_byte(Byte),
position(Bits, Len)}};
false ->
parse_string_loop(Rest@2, Len, <<Acc/bitstring, Byte>>)
end;
_ ->
{error, unexpected_end}
end.
-file("src/gleamson.gleam", 192).
-spec parse_string(bitstring(), integer()) -> {ok, {binary(), bitstring()}} |
{error, parse_error()}.
parse_string(Bits, Len) ->
parse_string_loop(Bits, Len, <<>>).
-file("src/gleamson.gleam", 126).
-spec parse_array_element(bitstring(), integer(), list(json())) -> {ok,
{json(), bitstring()}} |
{error, parse_error()}.
parse_array_element(Bits, Len, Acc) ->
gleam@result:'try'(
parse_value(Bits, Len),
fun(_use0) ->
{Value, Rest} = _use0,
Acc@1 = [Value | Acc],
Rest@1 = skip_whitespace(Rest),
case Rest@1 of
<<","/utf8, After/bitstring>> ->
parse_array_element(skip_whitespace(After), Len, Acc@1);
<<"]"/utf8, After@1/bitstring>> ->
{ok, {{array, lists:reverse(Acc@1)}, After@1}};
<<Byte, _/bitstring>> ->
{error,
{unexpected_byte,
describe_byte(Byte),
position(Rest@1, Len)}};
_ ->
{error, unexpected_end}
end
end
).
-file("src/gleamson.gleam", 118).
-spec parse_array(bitstring(), integer(), list(json())) -> {ok,
{json(), bitstring()}} |
{error, parse_error()}.
parse_array(Bits, Len, Acc) ->
case Bits of
<<"]"/utf8, Rest/bitstring>> ->
{ok, {{array, lists:reverse(Acc)}, Rest}};
_ ->
parse_array_element(Bits, Len, Acc)
end.
-file("src/gleamson.gleam", 153).
-spec parse_object_member(bitstring(), integer(), list({binary(), json()})) -> {ok,
{json(), bitstring()}} |
{error, parse_error()}.
parse_object_member(Bits, Len, Acc) ->
case Bits of
<<"\""/utf8, Rest/bitstring>> ->
gleam@result:'try'(
parse_string(Rest, Len),
fun(_use0) ->
{Key, Rest@1} = _use0,
Rest@2 = skip_whitespace(Rest@1),
case Rest@2 of
<<":"/utf8, After/bitstring>> ->
gleam@result:'try'(
parse_value(skip_whitespace(After), Len),
fun(_use0@1) ->
{Value, Rest@3} = _use0@1,
Acc@1 = [{Key, Value} | Acc],
Rest@4 = skip_whitespace(Rest@3),
case Rest@4 of
<<","/utf8, More/bitstring>> ->
parse_object_member(
skip_whitespace(More),
Len,
Acc@1
);
<<"}"/utf8, More@1/bitstring>> ->
{ok,
{{object, lists:reverse(Acc@1)},
More@1}};
<<Byte, _/bitstring>> ->
{error,
{unexpected_byte,
describe_byte(Byte),
position(Rest@4, Len)}};
_ ->
{error, unexpected_end}
end
end
);
<<Byte@1, _/bitstring>> ->
{error,
{unexpected_byte,
describe_byte(Byte@1),
position(Rest@2, Len)}};
_ ->
{error, unexpected_end}
end
end
);
<<Byte@2, _/bitstring>> ->
{error,
{unexpected_byte, describe_byte(Byte@2), position(Bits, Len)}};
_ ->
{error, unexpected_end}
end.
-file("src/gleamson.gleam", 141).
-spec parse_object(bitstring(), integer(), list({binary(), json()})) -> {ok,
{json(), bitstring()}} |
{error, parse_error()}.
parse_object(Bits, Len, Acc) ->
case Bits of
<<"}"/utf8, Rest/bitstring>> ->
{ok, {{object, lists:reverse(Acc)}, Rest}};
_ ->
parse_object_member(Bits, Len, Acc)
end.
-file("src/gleamson.gleam", 98).
-spec parse_value(bitstring(), integer()) -> {ok, {json(), bitstring()}} |
{error, parse_error()}.
parse_value(Bits, Len) ->
case Bits of
<<"null"/utf8, Rest/bitstring>> ->
{ok, {null, Rest}};
<<"true"/utf8, Rest@1/bitstring>> ->
{ok, {{bool, true}, Rest@1}};
<<"false"/utf8, Rest@2/bitstring>> ->
{ok, {{bool, false}, Rest@2}};
<<"\""/utf8, Rest@3/bitstring>> ->
gleam@result:'try'(
parse_string(Rest@3, Len),
fun(_use0) ->
{Value, Remainder} = _use0,
{ok, {{string, Value}, Remainder}}
end
);
<<"{"/utf8, Rest@4/bitstring>> ->
parse_object(skip_whitespace(Rest@4), Len, []);
<<"["/utf8, Rest@5/bitstring>> ->
parse_array(skip_whitespace(Rest@5), Len, []);
<<Byte, _/bitstring>> ->
case (Byte =:= 16#2D) orelse ((Byte >= 16#30) andalso (Byte =< 16#39)) of
true ->
parse_number(Bits, Len);
false ->
{error,
{unexpected_byte,
describe_byte(Byte),
position(Bits, Len)}}
end;
_ ->
{error, unexpected_end}
end.
-file("src/gleamson.gleam", 83).
?DOC(
" Parse JSON from a `BitArray`. Useful when the bytes come straight off the\n"
" wire and you would rather not allocate an intermediate `String`.\n"
).
-spec parse_bits(bitstring()) -> {ok, json()} | {error, parse_error()}.
parse_bits(Json) ->
Len = erlang:byte_size(Json),
gleam@result:'try'(
parse_value(skip_whitespace(Json), Len),
fun(_use0) ->
{Value, Rest} = _use0,
Trailing = skip_whitespace(Rest),
case Trailing of
<<>> ->
{ok, Value};
<<Byte, _/bitstring>> ->
{error,
{unexpected_byte,
describe_byte(Byte),
position(Trailing, Len)}};
_ ->
{error, unexpected_end}
end
end
).
-file("src/gleamson.gleam", 77).
?DOC(
" Parse a JSON string into a `Json` value.\n"
"\n"
" ```gleam\n"
" parse(\"[1, 2, 3]\")\n"
" // -> Ok(Array([Int(1), Int(2), Int(3)]))\n"
"\n"
" parse(\"[\")\n"
" // -> Error(UnexpectedEnd)\n"
" ```\n"
).
-spec parse(binary()) -> {ok, json()} | {error, parse_error()}.
parse(Json) ->
parse_bits(gleam_stdlib:identity(Json)).
-file("src/gleamson.gleam", 536).
-spec pad_hex4(integer()) -> binary().
pad_hex4(Code) ->
Hex = to_hex(Code),
case string:length(Hex) of
1 ->
<<"000"/utf8, Hex/binary>>;
2 ->
<<"00"/utf8, Hex/binary>>;
3 ->
<<"0"/utf8, Hex/binary>>;
_ ->
Hex
end.
-file("src/gleamson.gleam", 522).
-spec escape_codepoint(integer()) -> binary().
escape_codepoint(Cp) ->
case gleam_stdlib:identity(Cp) of
16#22 ->
<<"\\\""/utf8>>;
16#5C ->
<<"\\\\"/utf8>>;
16#08 ->
<<"\\b"/utf8>>;
16#0C ->
<<"\\f"/utf8>>;
16#0A ->
<<"\\n"/utf8>>;
16#0D ->
<<"\\r"/utf8>>;
16#09 ->
<<"\\t"/utf8>>;
Code when Code < 16#20 ->
<<"\\u"/utf8, (pad_hex4(Code))/binary>>;
_ ->
gleam_stdlib:utf_codepoint_list_to_string([Cp])
end.
-file("src/gleamson.gleam", 514).
-spec escape_string(binary()) -> gleam@string_tree:string_tree().
escape_string(Input) ->
_pipe = Input,
_pipe@1 = gleam@string:to_utf_codepoints(_pipe),
gleam@list:fold(
_pipe@1,
gleam@string_tree:new(),
fun(Tree, Cp) ->
gleam@string_tree:append(Tree, escape_codepoint(Cp))
end
).
-file("src/gleamson.gleam", 506).
-spec encode_pair({binary(), json()}) -> gleam@string_tree:string_tree().
encode_pair(Pair) ->
{Key, Value} = Pair,
_pipe = gleam_stdlib:identity(<<"\""/utf8>>),
_pipe@1 = gleam_stdlib:iodata_append(_pipe, escape_string(Key)),
_pipe@2 = gleam@string_tree:append(_pipe@1, <<"\":"/utf8>>),
gleam_stdlib:iodata_append(_pipe@2, to_string_tree(Value)).
-file("src/gleamson.gleam", 489).
-spec encode_object(list({binary(), json()})) -> gleam@string_tree:string_tree().
encode_object(Entries) ->
case Entries of
[] ->
gleam_stdlib:identity(<<"{}"/utf8>>);
[First | Rest] ->
Body = gleam@list:fold(
Rest,
encode_pair(First),
fun(Acc, Pair) -> _pipe = Acc,
_pipe@1 = gleam@string_tree:append(_pipe, <<","/utf8>>),
gleam_stdlib:iodata_append(_pipe@1, encode_pair(Pair)) end
),
_pipe@2 = gleam_stdlib:identity(<<"{"/utf8>>),
_pipe@3 = gleam_stdlib:iodata_append(_pipe@2, Body),
gleam@string_tree:append(_pipe@3, <<"}"/utf8>>)
end.
-file("src/gleamson.gleam", 472).
-spec encode_array(list(json())) -> gleam@string_tree:string_tree().
encode_array(Items) ->
case Items of
[] ->
gleam_stdlib:identity(<<"[]"/utf8>>);
[First | Rest] ->
Body = gleam@list:fold(
Rest,
to_string_tree(First),
fun(Acc, Item) -> _pipe = Acc,
_pipe@1 = gleam@string_tree:append(_pipe, <<","/utf8>>),
gleam_stdlib:iodata_append(_pipe@1, to_string_tree(Item)) end
),
_pipe@2 = gleam_stdlib:identity(<<"["/utf8>>),
_pipe@3 = gleam_stdlib:iodata_append(_pipe@2, Body),
gleam@string_tree:append(_pipe@3, <<"]"/utf8>>)
end.
-file("src/gleamson.gleam", 456).
?DOC(" Render a `Json` value to a `StringTree` (iodata).\n").
-spec to_string_tree(json()) -> gleam@string_tree:string_tree().
to_string_tree(Json) ->
case Json of
null ->
gleam_stdlib:identity(<<"null"/utf8>>);
{bool, true} ->
gleam_stdlib:identity(<<"true"/utf8>>);
{bool, false} ->
gleam_stdlib:identity(<<"false"/utf8>>);
{int, Value} ->
gleam_stdlib:identity(erlang:integer_to_binary(Value));
{float, Value@1} ->
gleam_stdlib:identity(gleam_stdlib:float_to_string(Value@1));
{string, Value@2} ->
_pipe = gleam_stdlib:identity(<<"\""/utf8>>),
_pipe@1 = gleam_stdlib:iodata_append(_pipe, escape_string(Value@2)),
gleam@string_tree:append(_pipe@1, <<"\""/utf8>>);
{array, Items} ->
encode_array(Items);
{object, Entries} ->
encode_object(Entries)
end.
-file("src/gleamson.gleam", 449).
?DOC(
" Render a `Json` value to a compact string.\n"
"\n"
" Prefer `to_string_tree` when feeding the BEAM's IO, which is optimised for\n"
" iodata.\n"
"\n"
" ```gleam\n"
" to_string(Array([Int(1), Int(2), Int(3)]))\n"
" // -> \"[1,2,3]\"\n"
" ```\n"
).
-spec to_string(json()) -> binary().
to_string(Json) ->
_pipe = Json,
_pipe@1 = to_string_tree(_pipe),
unicode:characters_to_binary(_pipe@1).
-file("src/gleamson.gleam", 556).
?DOC(
" Encode a list as a JSON array using a per-item encoder.\n"
"\n"
" ```gleam\n"
" array([\"a\", \"b\"], of: String)\n"
" // -> Array([String(\"a\"), String(\"b\")])\n"
" ```\n"
).
-spec array(list(DLW), fun((DLW) -> json())) -> json().
array(Items, Encode) ->
{array, gleam@list:map(Items, Encode)}.
-file("src/gleamson.gleam", 561).
?DOC(" Encode an `Option`, using `Null` for `None`.\n").
-spec nullable(gleam@option:option(DLY), fun((DLY) -> json())) -> json().
nullable(Value, Encode) ->
case Value of
{some, Inner} ->
Encode(Inner);
none ->
null
end.
-file("src/gleamson.gleam", 569).
?DOC(" Build an `Object` from a `Dict`.\n").
-spec from_dict(
gleam@dict:dict(DMA, DMB),
fun((DMA) -> binary()),
fun((DMB) -> json())
) -> json().
from_dict(Values, Keys, Encode) ->
{object,
gleam@dict:fold(
Values,
[],
fun(Acc, Key, Value) -> [{Keys(Key), Encode(Value)} | Acc] end
)}.
-file("src/gleamson.gleam", 586).
?DOC(" Look up a key in an object.\n").
-spec field(json(), binary()) -> {ok, json()} | {error, nil}.
field(Json, Name) ->
case Json of
{object, Entries} ->
gleam@list:key_find(Entries, Name);
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 598).
?DOC(
" Follow a path of object keys.\n"
"\n"
" ```gleam\n"
" get(value, at: [\"user\", \"name\"])\n"
" ```\n"
).
-spec get(json(), list(binary())) -> {ok, json()} | {error, nil}.
get(Json, Path) ->
gleam@list:try_fold(
Path,
Json,
fun(Current, Key) -> field(Current, Key) end
).
-file("src/gleamson.gleam", 603).
?DOC(" Index into an array.\n").
-spec index(json(), integer()) -> {ok, json()} | {error, nil}.
index(Json, I) ->
case Json of
{array, Items} when I >= 0 ->
gleam@list:first(gleam@list:drop(Items, I));
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 612).
?DOC(
" Convert an object to a `Dict` for repeated `O(1)` lookups. Later keys win\n"
" on duplicates.\n"
).
-spec to_dict(json()) -> {ok, gleam@dict:dict(binary(), json())} | {error, nil}.
to_dict(Json) ->
case Json of
{object, Entries} ->
{ok, maps:from_list(Entries)};
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 619).
-spec as_string(json()) -> {ok, binary()} | {error, nil}.
as_string(Json) ->
case Json of
{string, Value} ->
{ok, Value};
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 626).
-spec as_int(json()) -> {ok, integer()} | {error, nil}.
as_int(Json) ->
case Json of
{int, Value} ->
{ok, Value};
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 633).
-spec as_float(json()) -> {ok, float()} | {error, nil}.
as_float(Json) ->
case Json of
{float, Value} ->
{ok, Value};
{int, Value@1} ->
{ok, erlang:float(Value@1)};
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 641).
-spec as_bool(json()) -> {ok, boolean()} | {error, nil}.
as_bool(Json) ->
case Json of
{bool, Value} ->
{ok, Value};
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 723).
-spec join_trees(list(gleam@string_tree:string_tree()), binary()) -> gleam@string_tree:string_tree().
join_trees(Trees, Separator) ->
case Trees of
[] ->
gleam@string_tree:new();
[First | Rest] ->
gleam@list:fold(Rest, First, fun(Acc, Tree) -> _pipe = Acc,
_pipe@1 = gleam@string_tree:append(_pipe, Separator),
gleam_stdlib:iodata_append(_pipe@1, Tree) end)
end.
-file("src/gleamson.gleam", 698).
-spec pretty_object(list({binary(), json()}), integer(), integer()) -> gleam@string_tree:string_tree().
pretty_object(Entries, Depth, Spaces) ->
Item_indent = gleam@string:repeat(<<" "/utf8>>, (Depth + 1) * Spaces),
Close_indent = gleam@string:repeat(<<" "/utf8>>, Depth * Spaces),
Body = begin
_pipe = Entries,
_pipe@5 = gleam@list:map(
_pipe,
fun(Entry) ->
{Key, Value} = Entry,
_pipe@1 = gleam_stdlib:identity(Item_indent),
_pipe@2 = gleam@string_tree:append(_pipe@1, <<"\""/utf8>>),
_pipe@3 = gleam_stdlib:iodata_append(
_pipe@2,
escape_string(Key)
),
_pipe@4 = gleam@string_tree:append(_pipe@3, <<"\": "/utf8>>),
gleam_stdlib:iodata_append(
_pipe@4,
pretty(Value, Depth + 1, Spaces)
)
end
),
join_trees(_pipe@5, <<",\n"/utf8>>)
end,
_pipe@6 = gleam_stdlib:identity(<<"{\n"/utf8>>),
_pipe@7 = gleam_stdlib:iodata_append(_pipe@6, Body),
_pipe@8 = gleam@string_tree:append(_pipe@7, <<"\n"/utf8>>),
_pipe@9 = gleam@string_tree:append(_pipe@8, Close_indent),
gleam@string_tree:append(_pipe@9, <<"}"/utf8>>).
-file("src/gleamson.gleam", 681).
-spec pretty_array(list(json()), integer(), integer()) -> gleam@string_tree:string_tree().
pretty_array(Items, Depth, Spaces) ->
Item_indent = gleam@string:repeat(<<" "/utf8>>, (Depth + 1) * Spaces),
Close_indent = gleam@string:repeat(<<" "/utf8>>, Depth * Spaces),
Body = begin
_pipe = Items,
_pipe@2 = gleam@list:map(
_pipe,
fun(Item) -> _pipe@1 = gleam_stdlib:identity(Item_indent),
gleam_stdlib:iodata_append(
_pipe@1,
pretty(Item, Depth + 1, Spaces)
) end
),
join_trees(_pipe@2, <<",\n"/utf8>>)
end,
_pipe@3 = gleam_stdlib:identity(<<"[\n"/utf8>>),
_pipe@4 = gleam_stdlib:iodata_append(_pipe@3, Body),
_pipe@5 = gleam@string_tree:append(_pipe@4, <<"\n"/utf8>>),
_pipe@6 = gleam@string_tree:append(_pipe@5, Close_indent),
gleam@string_tree:append(_pipe@6, <<"]"/utf8>>).
-file("src/gleamson.gleam", 670).
-spec pretty(json(), integer(), integer()) -> gleam@string_tree:string_tree().
pretty(Json, Depth, Spaces) ->
case Json of
{array, []} ->
gleam_stdlib:identity(<<"[]"/utf8>>);
{object, []} ->
gleam_stdlib:identity(<<"{}"/utf8>>);
{array, Items} ->
pretty_array(Items, Depth, Spaces);
{object, Entries} ->
pretty_object(Entries, Depth, Spaces);
_ ->
to_string_tree(Json)
end.
-file("src/gleamson.gleam", 664).
?DOC(" Like `to_string_pretty`, but with a configurable number of spaces per level.\n").
-spec to_string_pretty_with(json(), integer()) -> binary().
to_string_pretty_with(Json, Spaces) ->
_pipe = Json,
_pipe@1 = pretty(_pipe, 0, Spaces),
unicode:characters_to_binary(_pipe@1).
-file("src/gleamson.gleam", 659).
?DOC(
" Render a `Json` value as indented, human-readable text using two spaces per\n"
" nesting level.\n"
"\n"
" ```gleam\n"
" to_string_pretty(Object([#(\"a\", Int(1))]))\n"
" // -> \"{\\n \\\"a\\\": 1\\n}\"\n"
" ```\n"
).
-spec to_string_pretty(json()) -> binary().
to_string_pretty(Json) ->
to_string_pretty_with(Json, 2).
-file("src/gleamson.gleam", 768).
-spec value_for(list({binary(), json()}), binary()) -> json().
value_for(Entries, Key) ->
case gleam@list:key_find(Entries, Key) of
{ok, Value} ->
Value;
{error, _} ->
null
end.
-file("src/gleamson.gleam", 775).
-spec upsert(list({binary(), json()}), binary(), json()) -> list({binary(),
json()}).
upsert(Entries, Key, Value) ->
case gleam@list:any(Entries, fun(Kv) -> erlang:element(1, Kv) =:= Key end) of
true ->
gleam@list:map(
Entries,
fun(Kv@1) -> case erlang:element(1, Kv@1) =:= Key of
true ->
{Key, Value};
false ->
Kv@1
end end
);
false ->
lists:append(Entries, [{Key, Value}])
end.
-file("src/gleamson.gleam", 755).
-spec merge_entries(list({binary(), json()}), list({binary(), json()})) -> list({binary(),
json()}).
merge_entries(Base, Patch) ->
gleam@list:fold(
Patch,
Base,
fun(Acc, Entry) ->
{Key, Value} = Entry,
case Value of
null ->
gleam@list:filter(
Acc,
fun(Kv) -> erlang:element(1, Kv) /= Key end
);
_ ->
upsert(Acc, Key, merge(value_for(Acc, Key), Value))
end
end
).
-file("src/gleamson.gleam", 747).
?DOC(
" Merge `patch` into `base`. Objects are merged recursively, a `Null` in the\n"
" patch removes that key, and any non-object patch replaces the base value.\n"
" Handy for layering configuration or applying partial updates.\n"
"\n"
" ```gleam\n"
" merge(into: Object([#(\"a\", Int(1)), #(\"b\", Int(2))]), patch: Object([#(\"b\", Null)]))\n"
" // -> Object([#(\"a\", Int(1))])\n"
" ```\n"
).
-spec merge(json(), json()) -> json().
merge(Base, Patch) ->
case {Base, Patch} of
{{object, Base_entries}, {object, Patch_entries}} ->
{object, merge_entries(Base_entries, Patch_entries)};
{_, _} ->
Patch
end.
-file("src/gleamson.gleam", 814).
-spec elements_equal(list(json()), list(json())) -> boolean().
elements_equal(A, B) ->
case {A, B} of
{[], []} ->
true;
{[X | Xs], [Y | Ys]} ->
semantically_equal(X, Y) andalso elements_equal(Xs, Ys);
{_, _} ->
false
end.
-file("src/gleamson.gleam", 799).
?DOC(
" Compare two values for equality while ignoring the order of object keys.\n"
" Arrays stay order-sensitive, since JSON arrays are ordered. Great for tests\n"
" where `==` would be too strict about key order.\n"
).
-spec semantically_equal(json(), json()) -> boolean().
semantically_equal(A, B) ->
case {A, B} of
{{object, Entries_a}, {object, Entries_b}} ->
(erlang:length(Entries_a) =:= erlang:length(Entries_b)) andalso gleam@list:all(
Entries_a,
fun(Entry) ->
case gleam@list:key_find(
Entries_b,
erlang:element(1, Entry)
) of
{ok, Value_b} ->
semantically_equal(
erlang:element(2, Entry),
Value_b
);
{error, _} ->
false
end
end
);
{{array, Items_a}, {array, Items_b}} ->
elements_equal(Items_a, Items_b);
{_, _} ->
A =:= B
end.
-file("src/gleamson.gleam", 871).
-spec unescape_token(binary()) -> binary().
unescape_token(Token) ->
_pipe = Token,
_pipe@1 = gleam@string:replace(_pipe, <<"~1"/utf8>>, <<"/"/utf8>>),
gleam@string:replace(_pipe@1, <<"~0"/utf8>>, <<"~"/utf8>>).
-file("src/gleamson.gleam", 859).
-spec resolve_token(json(), binary()) -> {ok, json()} | {error, nil}.
resolve_token(Json, Token) ->
case Json of
{object, _} ->
field(Json, Token);
{array, _} ->
case gleam_stdlib:parse_int(Token) of
{ok, I} ->
index(Json, I);
{error, _} ->
{error, nil}
end;
_ ->
{error, nil}
end.
-file("src/gleamson.gleam", 849).
-spec follow_tokens(json(), list(binary())) -> {ok, json()} | {error, nil}.
follow_tokens(Json, Tokens) ->
case Tokens of
[] ->
{ok, Json};
[Token | Rest] ->
gleam@result:'try'(
resolve_token(Json, Token),
fun(Child) -> follow_tokens(Child, Rest) end
)
end.
-file("src/gleamson.gleam", 836).
?DOC(
" Look up a value by a JSON Pointer string, e.g. `\"/user/items/0/id\"`.\n"
"\n"
" An empty string returns the whole document. Object keys containing `/` or\n"
" `~` are escaped as `~1` and `~0` respectively, per RFC 6901.\n"
"\n"
" ```gleam\n"
" pointer(value, \"/a/b/1\") // 2nd element of a.b\n"
" pointer(value, \"\") // the whole value\n"
" pointer(value, \"/a~1b\") // the key \"a/b\"\n"
" ```\n"
).
-spec pointer(json(), binary()) -> {ok, json()} | {error, nil}.
pointer(Json, Path) ->
case Path of
<<""/utf8>> ->
{ok, Json};
_ ->
case gleam@string:split(Path, <<"/"/utf8>>) of
[<<""/utf8>> | Tokens] ->
follow_tokens(
Json,
gleam@list:map(Tokens, fun unescape_token/1)
);
_ ->
{error, nil}
end
end.