Skip to content

Commit

Permalink
Fix compatibility (#5)
Browse files Browse the repository at this point in the history
* Create tests

* Create CI file

* Replace dynamic with term type

* Fix non suported map comprehension
  • Loading branch information
williamthome authored Aug 4, 2024
1 parent cd153c1 commit efcd0a6
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 21 deletions.
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
name: CI

"on":
push:
branches:
- main
pull_request:
branches:
- "*"
workflow_dispatch: {}
merge_group:

concurrency:
group: ${{github.workflow}}-${{github.ref}}
cancel-in-progress: true

jobs:
ci:
name: OTP-${{matrix.otp-version}}

runs-on: ${{matrix.os}}

strategy:
matrix:
otp-version: [24, 25, 26]
os: [ubuntu-24.04]

steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

- uses: erlef/setup-beam@b9c58b0450cd832ccdb3c17cc156a47065d2114f # v1.18.1
id: setup-beam
with:
otp-version: ${{matrix.otp-version}}
rebar3-version: 3.23.0

- name: Restore _build
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: _build
key: "_build-cache-for\
-os-${{runner.os}}\
-otp-${{steps.setup-beam.outputs.otp-version}}\
-rebar3-${{steps.setup-beam.outputs.rebar3-version}}\
-hash-${{hashFiles('rebar.lock')}}-${{hashFiles('rebar.config')}}"

- name: Restore rebar3's cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ~/.cache/rebar3
key: "rebar3-cache-for\
-os-${{runner.os}}\
-otp-${{steps.setup-beam.outputs.otp-version}}\
-rebar3-${{steps.setup-beam.outputs.rebar3-version}}\
-hash-${{hashFiles('rebar.lock')}}"

- name: Test
run: |
rebar3 ct
44 changes: 23 additions & 21 deletions src/json.erl
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
%% Encoding implementation
%%

-type encoder() :: fun((dynamic(), encoder()) -> iodata()).
-type encoder() :: fun((term(), encoder()) -> iodata()).

-type encode_value() ::
integer()
Expand Down Expand Up @@ -156,7 +156,7 @@ encode(Term) -> encode(Term, fun do_encode/2).
%% <<"{\"a\":[],\"b\":1}">>
%% '''
%% @end
-spec encode(dynamic(), encoder()) -> iodata().
-spec encode(term(), encoder()) -> iodata().
encode(Term, Encoder) when is_function(Encoder, 2) ->
Encoder(Term, Encoder).

Expand All @@ -165,11 +165,11 @@ encode(Term, Encoder) when is_function(Encoder, 2) ->
%%
%% Recursively calls `Encode' on all the values in `Value'.
%% @end
-spec encode_value(dynamic(), encoder()) -> iodata().
-spec encode_value(term(), encoder()) -> iodata().
encode_value(Value, Encode) ->
do_encode(Value, Encode).

-spec do_encode(dynamic(), encoder()) -> iodata().
-spec do_encode(term(), encoder()) -> iodata().
do_encode(Value, Encode) when is_atom(Value) ->
encode_atom(Value, Encode);
do_encode(Value, _Encode) when is_binary(Value) ->
Expand Down Expand Up @@ -231,12 +231,14 @@ list_loop([Elem | Rest], Encode) -> [$,, Encode(Elem, Encode) | list_loop(Rest,
%%
%% Accepts maps with atom, binary, integer, or float keys.
%% @end
-spec encode_map(encode_map(dynamic()), encoder()) -> iodata().
-spec encode_map(encode_map(term()), encoder()) -> iodata().
encode_map(Map, Encode) when is_map(Map) ->
do_encode_map(Map, Encode).

do_encode_map(Map, Encode) when is_function(Encode, 2) ->
encode_object([[$,, key(Key, Encode), $: | Encode(Value, Encode)] || Key := Value <- Map]).
encode_object(maps:fold(fun(Key, Value, Acc) ->
[[$,, key(Key, Encode), $: | Encode(Value, Encode)] | Acc]
end, [], Map)).

%% @doc
%% Encoder for maps as JSON objects.
Expand Down Expand Up @@ -525,13 +527,13 @@ error_info(Skip) ->
-define(ARRAY, array).
-define(OBJECT, object).

-type from_binary_fun() :: fun((binary()) -> dynamic()).
-type array_start_fun() :: fun((Acc :: dynamic()) -> ArrayAcc :: dynamic()).
-type array_push_fun() :: fun((Value :: dynamic(), Acc :: dynamic()) -> NewAcc :: dynamic()).
-type array_finish_fun() :: fun((ArrayAcc :: dynamic(), OldAcc :: dynamic()) -> {dynamic(), dynamic()}).
-type object_start_fun() :: fun((Acc :: dynamic()) -> ObjectAcc :: dynamic()).
-type object_push_fun() :: fun((Key :: dynamic(), Value :: dynamic(), Acc :: dynamic()) -> NewAcc :: dynamic()).
-type object_finish_fun() :: fun((ObjectAcc :: dynamic(), OldAcc :: dynamic()) -> {dynamic(), dynamic()}).
-type from_binary_fun() :: fun((binary()) -> term()).
-type array_start_fun() :: fun((Acc :: term()) -> ArrayAcc :: term()).
-type array_push_fun() :: fun((Value :: term(), Acc :: term()) -> NewAcc :: term()).
-type array_finish_fun() :: fun((ArrayAcc :: term(), OldAcc :: term()) -> {term(), term()}).
-type object_start_fun() :: fun((Acc :: term()) -> ObjectAcc :: term()).
-type object_push_fun() :: fun((Key :: term(), Value :: term(), Acc :: term()) -> NewAcc :: term()).
-type object_finish_fun() :: fun((ObjectAcc :: term(), OldAcc :: term()) -> {term(), term()}).

-type decoders() :: #{
array_start => array_start_fun(),
Expand Down Expand Up @@ -559,7 +561,7 @@ error_info(Skip) ->
null = null :: term()
}).

-type acc() :: dynamic().
-type acc() :: term().
-type stack() :: [?ARRAY | ?OBJECT | binary() | acc()].
-type decode() :: #decode{}.

Expand Down Expand Up @@ -657,8 +659,8 @@ decode(Binary) when is_binary(Binary) ->
%% {#{foo => 1},ok,<<>>}
%% '''
%% @end
-spec decode(binary(), dynamic(), decoders()) ->
{Result :: dynamic(), Acc :: dynamic(), binary()}.
-spec decode(binary(), term(), decoders()) ->
{Result :: term(), Acc :: term(), binary()}.
decode(Binary, Acc0, Decoders) when is_binary(Binary) ->
Decode = maps:fold(fun parse_decoder/3, #decode{}, Decoders),
case value(Binary, Binary, 0, Acc0, [], Decode) of
Expand All @@ -679,8 +681,8 @@ decode(Binary, Acc0, Decoders) when is_binary(Binary) ->
%% returns `{continue, State}' for incomplete data,
%% the `State' can be fed to the `decode_continue/2' function when more data is available.
%% @end
-spec decode_start(binary(), dynamic(), decoders()) ->
{Result :: dynamic(), Acc :: dynamic(), binary()} | {continue, continuation_state()}.
-spec decode_start(binary(), term(), decoders()) ->
{Result :: term(), Acc :: term(), binary()} | {continue, continuation_state()}.
decode_start(Binary, Acc, Decoders) when is_binary(Binary) ->
Decode = maps:fold(fun parse_decoder/3, #decode{}, Decoders),
value(Binary, Binary, 0, Acc, [], Decode).
Expand All @@ -704,7 +706,7 @@ decode_start(Binary, Acc, Decoders) when is_binary(Binary) ->
%% '''
%% @end
-spec decode_continue(binary() | end_of_input, Opaque::term()) ->
{Result :: dynamic(), Acc :: dynamic(), binary()} | {continue, continuation_state()}.
{Result :: term(), Acc :: term(), binary()} | {continue, continuation_state()}.
decode_continue(end_of_input, State) ->
case State of
{_, Acc, [], _Decode, {number, Val}} ->
Expand Down Expand Up @@ -903,7 +905,7 @@ string_ascii(Binary, Original, Skip, Acc, Stack, Decode, Len) ->
string(Other, Original, Skip, Acc, Stack, Decode, Len)
end.

-spec string(binary(), binary(), integer(), acc(), stack(), decode(), integer()) -> dynamic().
-spec string(binary(), binary(), integer(), acc(), stack(), decode(), integer()) -> term().
string(<<Byte, Rest/bits>>, Orig, Skip, Acc, Stack, Decode, Len) when ?is_ascii_plain(Byte) ->
string(Rest, Orig, Skip, Acc, Stack, Decode, Len + 1);
string(<<$\\, Rest/bits>>, Orig, Skip, Acc, Stack, Decode, Len) ->
Expand Down Expand Up @@ -946,7 +948,7 @@ string_ascii(Binary, Original, Skip, Acc, Stack, Decode, Start, Len, SAcc) ->
string(Other, Original, Skip, Acc, Stack, Decode, Start, Len, SAcc)
end.

-spec string(binary(), binary(), integer(), acc(), stack(), decode(), integer(), integer(), binary()) -> dynamic().
-spec string(binary(), binary(), integer(), acc(), stack(), decode(), integer(), integer(), binary()) -> term().
string(<<Byte, Rest/bits>>, Orig, Skip, Acc, Stack, Decode, Start, Len, SAcc) when ?is_ascii_plain(Byte) ->
string(Rest, Orig, Skip, Acc, Stack, Decode, Start, Len + 1, SAcc);
string(<<$\\, Rest/bits>>, Orig, Skip, Acc, Stack, Decode, Start, Len, SAcc) ->
Expand Down
36 changes: 36 additions & 0 deletions test/json_polyfill_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-module(json_polyfill_SUITE).
-behaviour(ct_suite).
-include_lib("stdlib/include/assert.hrl").
-compile([export_all, nowarn_export_all]).

all() ->
[encode, decode].

encode(Config) when is_list(Config) ->
ExpectA = [91,<<"null">>,44,
["{",
[[34,<<"foo">>,34],58,34,<<"bar">>,34],
[[44,[34,<<"bar">>,34],58,34,<<"baz">>,34]],
"}"],
93],
ExpectB = [91,<<"null">>,44,
["{",
[[34,<<"bar">>,34],58,34,<<"baz">>,34],
[[44,[34,<<"foo">>,34],58,34,<<"bar">>,34]],
"}"],
93],
Result = json:encode([null, #{foo => bar, bar => baz}]),
?assert(Result =:= ExpectA orelse Result =:= ExpectB).

decode(Config) when is_list(Config) ->
ExpectA = [null,#{<<"bar">> => <<"baz">>,<<"foo">> => <<"bar">>}],
ExpectB = [null,#{<<"foo">> => <<"bar">>,<<"bar">> => <<"baz">>}],
Result = json:decode(iolist_to_binary(
[91,<<"null">>,44,
["{",
[[34,<<"foo">>,34],58,34,<<"bar">>,34],
[[44,[34,<<"bar">>,34],58,34,<<"baz">>,34]],
"}"],
93]
)),
?assert(Result =:= ExpectA orelse Result =:= ExpectB).

0 comments on commit efcd0a6

Please sign in to comment.