View Source Euneus

An incredibly flexible and performant JSON parser and generator.

Euneus is a rewrite of Thoas.

Like Thoas, both the parser and generator fully conform to RFC 8259 and ECMA 404.

Table of Contents

Installation

Erlang

% rebar.config
{deps, [{euneus, "1.0.2"}]}

Elixir

# mix.exs
def deps do
  [{:euneus, "~> 1.0"}]
end

Basic Usage

1> {ok, JSON} = euneus:encode_to_binary(#{name => #{english => <<"Charmander">>, japanese => <<"ヒトカゲ"/utf8>>}, type => [fire], profile => #{height => 0.6, weight => 8}, ability => #{0 => <<"Blaze">>, 1 => undefined}}).
{ok, <<"{\"name\":{\"english\":\"Charmander\",\"japanese\":\"ヒトカゲ\"},\"profile\":{\"height\":0.6,\"weight\":8},\"type\":[\"fire\"],\"ability\":{\"0\":\"Blaze\",\"1\":null}}">>}

2> euneus:decode(JSON).
{ok,#{<<"ability">> =>
          #{<<"0">> => <<"Blaze">>,<<"1">> => undefined},
      <<"name">> =>
          #{<<"english">> => <<"Charmander">>,
            <<"japanese">> =>
                <<227,131,146,227,131,136,227,130,171,227,130,178>>},
      <<"profile">> => #{<<"height">> => 0.6,<<"weight">> => 8},
      <<"type">> => [<<"fire">>]}}

3> euneus:decode(JSON, #{
    keys => fun
        (<<Char>> = Key, _Opts) when Char >= $0, Char =< $9 ->
            binary_to_integer(Key);
        (Key, _Opts) ->
            binary_to_existing_atom(Key)
    end
}).
{ok,#{name =>
          #{english => <<"Charmander">>,
            japanese =>
                <<227,131,146,227,131,136,227,130,171,227,130,178>>},
      profile => #{height => 0.6,weight => 8},
      type => [<<"fire">>],
      ability => #{0 => <<"Blaze">>,1 => undefined}}}

Data Mapping

[!TIP]

More types can be handled by using custom plugins. Please see the Plugins section for more info.

Erlang ->Encode Options ->JSON ->Decode Options ->Erlang
undefined#{}null#{}undefined
undefined#{}null#{null_term => nil}nil
true#{}true#{}true
false#{}false#{}false
abc#{}"abc"#{}<<"abc">>
"abc"#{}[97,98,99]#{}"abc"
<<"abc">>#{}"abc"#{}<<"abc">>
123#{}123#{}123
123.45600#{}123.456#{}123.456
[<<"foo">>,true,0,undefined]#{}["foo",true,0,null]#{}[<<"foo">>,true,0,undefined]
#{foo => bar}#{}{"foo":"bar"}#{}#{<<"foo">> => <<"bar">>}
#{foo => bar}#{}{"foo":"bar"}#{keys => to_existing_atom}#{foo => <<"bar">>}
#{0 => 0}#{}{"0":0}#{keys => to_integer}#{0 => 0}
{{1970,1,1},{0,0,0}}#{plugins => [datetime]}"1970-01-01T00:00:00Z"#{plugins => [datetime]}{{1970,1,1},{0,0,0}}
{127,0,0,1}#{plugins => [inet]}"127.0.0.1"#{plugins => [inet]}{127,0,0,1}
{16#3ffe,16#b80,16#1f8d,16#2,16#204,16#acff,16#fe17,16#bf38}#{plugins => [inet]}"3ffe:b80:1f8d:2:204:acff:fe17:bf38"#{plugins => [inet]}{16#3ffe,16#b80,16#1f8d,16#2,16#204,16#acff,16#fe17,16#bf38}
<0.92.0>#{plugins => [pid]}"<0.92.0>"#{plugins => [pid]}<0.92.0>
#Port<0.1>#{plugins => [port]}"#Port<0.1>"#{plugins => [port]}#Port<0.1>
[{foo, bar}]#{plugins => [proplist]}{\"foo\":\"bar\"}#{plugins => [proplist]}#{<<"foo">> => <<"bar">>}
#Ref<0.957048870.857473026.108035>#{plugins => [reference]}"#Ref<0.957048870.857473026.108035>"#{plugins => [reference]}#Ref<0.957048870.857473026.108035>
{0,0,0}#{plugins => [timestamp]}"1970-01-01T00:00:00.000Z"#{plugins => [timestamp]}{0,0,0}
{myrecord, val}#{unhandled_encoder => fun({myrecord, Val}, Opts) -> <br> euneus_encoder:encode_list([myrecord, #{key => Val}], Opts)<br><br>end})["myrecord", {"key":"val"}]#{arrays => fun([<<"myrecord">>, #{<<"key">> := Val}], _Opts) -><br> {myrecord, binary_to_atom(Val)}<br>end}{myrecord, val}

Why not more built-in types?

The goal of Euneus is to have built-in types that can be commonly encoded and decoded, but the range of types can be easily extended by using plugins. Please see the Plugins section for more info.

Note about proplists

Proplists are not handled by Euneus by default.

There are three options:

  1. Use the built-in, or create your own, proplist plugin;
  2. Convert proplists to maps before the encoding;
  3. Override the list_encoder option in the encoder to handle them, for example:
1> Options = #{
       list_encoder => fun
           ([{K, _} | _] = Proplist, Opts)
             when is_binary(K); is_atom(K); is_integer(K) ->
               Map = proplists:to_map(Proplist),
               euneus_encoder:encode_map(Map, Opts);
           (List, Opts) ->
               euneus_encoder:encode_list(List, Opts)
       end
   }.

2> Proplist = [{foo, bar}, {bar, [{0, ok}]}].

3> euneus:encode_to_binary(Proplist, Options).
{ok,<<"{\"foo\":\"bar\",\"bar\":{\"0\":\"ok\"}}">>}

The reason for that is because it's impossible to know when a list is a proplist and also because a proplist cannot be decoded. Please see the Why not more built-in types? section for more info about this decision.

Plugins

Euneus has a mechanism to easily plug in encoders and decoders. You can use the built-in plugins to handle common types or create your own in a module by implementing the euneus_plugin behavior.

If you have a built-in plugin suggestion, feel free to open a new issue to discuss it.

[!IMPORTANT] The plugins mechanism deprecated the datetime_encoder and the timestamp_encoder option in favor of the datetime and timestamp plugins.

Usage

Encode

euneus:encode(Term, #{plugins => [
    % list of built-in or custom plugins
]})

Decode

euneus:decode(Term, #{plugins => [
    % list of built-in or custom plugins
]})

Built-in Plugins

datetime

Encodes calendar:datetime() to ISO8601 as JSON string and decodes it back, for example:

1> {ok, JSON} = euneus:encode_to_binary({{1970,1,1},{0,0,0}}, #{plugins => [datetime]}).
{ok,<<"\"1970-01-01T00:00:00Z\"">>}

2> euneus:decode(JSON, #{plugins => [datetime]}).
{ok,{{1970,1,1},{0,0,0}}}

inet

Encodes inet:ip_address() to IPV4 or IPV6 as JSON string and decodes it back, for example:

1> {ok, JSON} = euneus:encode_to_binary({127,0,0,1}, #{plugins => [inet]}).
{ok,<<"\"127.0.0.1\"">>}

2> euneus:decode(JSON, #{plugins => [inet]}).
{ok,{127,0,0,1}}

pid

Encodes erlang:pid() to JSON string and decodes it back, for example:

1> {ok, JSON} = euneus:encode_to_binary(list_to_pid("<0.92.0>"), #{plugins => [pid]}).
{ok,<<"\"<0.92.0>\"">>}

2> euneus:decode(JSON, #{plugins => [pid]}).
{ok,<0.92.0>}

port

Encodes erlang:port() to JSON string and decodes it back, for example:

1> {ok, JSON} = euneus:encode_to_binary(list_to_port("#Port<0.1>"), #{plugins => [port]}).
{ok,<<"\"#Port<0.1>\"">>}

2> euneus:decode(JSON, #{plugins => [port]}).
{ok,#Port<0.1>}

proplist

Encodes [{binary() | atom() | integer(), term()}] to JSON object, for example:

1> {ok, JSON} = euneus:encode_to_binary([{foo, bar}], #{plugins => [proplist]}).
{ok,<<"{\"foo\":\"bar\"}">>}

reference

Encodes erlang:reference() to JSON string and decodes it back, for example:

1> {ok, JSON} = euneus:encode_to_binary(make_ref(), #{plugins => [reference]}).
{ok,<<"\"#Ref<0.957048870.857473026.108035>\"">>}

2> euneus:decode(JSON, #{plugins => [reference]}).
{ok,#Ref<0.957048870.857473026.108035>}

timestamp

Encodes erlang:timestamp/0 to ISO8601 as JSON string and decodes it back, for example:

1> {ok, JSON} = euneus:encode_to_binary({0,0,0}, #{plugins => [timestamp]}).
{ok,<<"\"1970-01-01T00:00:00.000Z\"">>}

2> euneus:decode(JSON, #{plugins => [timestamp]}).
{ok,{0,0,0}}

Differences to Thoas

The main difference between Euneus to Thoas is that Euneus gives more control to encoding or decoding data. All encode functions can be overridden and extended and all decoded data can be overridden and transformed. Also, there is no plugin mechanism in Thoas.

Encode

Available encode options:

#{
    %% nulls defines what terms will be replaced with the null literal (default: ['undefined']).
    nulls => nonempty_list(),
    %% binary_encoder allow override the binary() encoding.
    binary_encoder => function((binary(), euneus_encoder:options()) -> iolist()),
    %% atom_encoder allow override the atom() encoding.
    atom_encoder => function((atom(), euneus_encoder:options()) -> iolist()),
    %% integer_encoder allow override the integer() encoding.
    integer_encoder => function((integer(), euneus_encoder:options()) -> iolist()),
    %% float_encoder allow override the float() encoding.
    float_encoder => function((float(), euneus_encoder:options()) -> iolist()),
    %% list_encoder allow override the list() encoding.
    list_encoder => function((list(), euneus_encoder:options()) -> iolist()),
    %% map_encoder allow override the map() encoding.
    map_encoder => function((map(), euneus_encoder:options()) -> iolist()),
    %% unhandled_encoder allow encode any custom term (default: raise unsupported_type error).
    unhandled_encoder => function((term(), euneus_encoder:options()) -> iolist()),
    %% escaper allow override the binary escaping (default: json).
    escaper => json
             | html
             | javascript
             | unicode
             | function((binary(), euneus_encoder:options()) -> iolist()),
    error_handler => function(( error | exit | throw
                              , term()
                              , erlang:stacktrace() ) -> euneus_encoder:result()),
    %% plugins extends the encode types.
    plugins => datetime
             | inet
             | pid
             | port
             | proplist
             | reference
             | timestamp
             | module() % that implements the `euneus_plugin` behavior.
}

For example:

1> EncodeOpts = #{
       binary_encoder => fun
           (<<"foo">>, Opts) ->
               euneus_encoder:encode_binary(<<"bar">>, Opts);
           (Bin, Opts) ->
               euneus_encoder:encode_binary(Bin, Opts)
       end,
       unhandled_encoder => fun
           ({_, _, _, _} = Ip, Opts) ->
               case inet:ntoa(Ip) of
                   {error, einval} ->
                       throw(invalid_ip);
                   IpStr ->
                       IpBin = list_to_binary(IpStr),
                       euneus_encoder:encode_binary(IpBin, Opts)
               end;
           (Term, Opts) ->
               euneus_encoder:throw_unsupported_type_error(Term, Opts)
       end,
       error_handler => fun
           (throw, invalid_ip, _Stacktrace) ->
               {error, invalid_ip};
           (Class, Reason, Stacktrace) ->
               euneus_encoder:handle_error(Class, Reason, Stacktrace)
       end
   }.

2> Data = #{<<"foo">> => bar, ipv4 => {127,0,0,1}, none => undefined}.

3> euneus:encode_to_binary(Data, EncodeOpts).
{ok, <<"{\"bar\":\"bar\",\"ipv4\":\"127.0.0.1\",\"none\":null}">>}

4> euneus:encode_to_binary({1270,0,0,1}, EncodeOpts).
{error, invalid_ip}

Decode

Available decode options:

#{
    %% null_term is the null literal override (default: 'undefined').
    null_term => term(),
    %% arrays allow override any array/list().
    arrays => function((list(), euneus_decoder:options()) -> term()),
    %% objects allow override any object/map().
    objects => function((map(), euneus_decoder:options()) -> term()),
    %% keys allow override the keys from JSON objects.
    keys => copy
          | to_atom
          | to_existing_atom
          | to_integer
          | function((binary(), euneus_decoder:options()) -> term()),
    %% values allow override any other term, like array item or object value.
    values => copy
            | to_atom
            | to_existing_atom
            | to_integer
            | function((binary(), euneus_decoder:options()) -> term()),
    %% plugins extends the decode types.
    plugins => datetime
             | inet
             | pid
             | port
             | reference
             | timestamp
             | module() % that implements the `euneus_plugin` behavior.
}

For example:

1> DecodeOpts = #{
      null_term => nil,
      keys => fun
          (<<"bar">>, _Opts) ->
              foo;
          (Key, _Opts) ->
              binary_to_atom(Key)
      end,
      values => fun
          (<<"127.0.0.1">>, _Opts) ->
              {127, 0, 0, 1};
          (Value, _Opts) ->
              Value
      end
   }.

2> JSON = <<"{\"bar\":\"bar\",\"ipv4\":\"127.0.0.1\",\"none\":null}">>.

3> euneus:decode(JSON, DecodeOpts).
{ok,#{foo => <<"bar">>,
      ipv4 => {127,0,0,1},
      none => nil}}

Resuming

Euneus permits resuming the decoding when an invalid token is found. Any value can replace the invalid token by overriding the error_handler option, e.g.:

1> ErrorHandler = fun
      (throw, {{token, Token}, Rest, Opts, Input, Pos, Buffer}, _Stacktrace) ->
          Replacement = foo,
          euneus_decoder:resume(Token, Replacement, Rest, Opts, Input, Pos, Buffer);
      (Class, Reason, Stacktrace) ->
          euneus_decoder:handle_error(Class, Reason, Stacktrace)
   end.

2> Opts = #{error_handler => ErrorHandler}.

3> euneus:decode(<<"[1e999,1e999,{\"foo\": 1e999}]">>, Opts).
{ok,[foo,foo,#{<<"foo">> => foo}]}

[!NOTE]

By using euneus_decoder:resume/6 the replacement will be the null_term option.

Why Euneus over Thoas?

Thoas is incredible, works performant and perfectly fine, but Euneus is more flexible, permitting more customizations, and is more performant than Thoas. See the benchmarks.

The motivation for Euneus is this PR.

Benchmarks

All the latest runs details can be found under the runs directory in the benchmark project.

Encode

Smart encoding

This benchmark uses the JSON smart module via the euneus:encode/1 function. Smart modules only receive the input as the argument, so no option is available to set. Smart modules are the fastest euneus modules.

Blockchain
Name IPS Slower
jiffy 11.14 K  
Jason 4.60 K 2.42x
euneus 4.49 K 2.48x
thoas 3.78 K 2.94x
jsone 2.54 K 4.39x
jsx 1.00 K 11.18x
Giphy
Name IPS Slower
jiffy 1162.06  
Jason 429.19 2.71x
euneus 427.23 2.72x
thoas 403.55 2.88x
jsone 213.42 5.44x
jsx 78.73 14.76x
GitHub
Name IPS Slower
jiffy 3.55 K  
Jason 1.51 K 2.35x
euneus 1.45 K 2.44x
thoas 1.30 K 2.72x
jsone 0.67 K 5.34x
jsx 0.197 K 18.01x
GovTrack
Name IPS Slower
jiffy 52.04  
Jason 17.91 2.91x
thoas 16.56 3.14x
euneus 14.72 3.53x
jsone 8.58 6.06x
jsx 3.07 16.95x
Issue 90
Name IPS Slower
jiffy 36.73  
Jason 27.44 1.34x
euneus 23.24 1.58x
thoas 17.01 2.16x
jsone 16.65 2.21x
jsx 8.75 4.2x
JSON Generator
Name IPS Slower
jiffy 1191.94  
euneus 417.78 2.85x
Jason 411.69 2.9x
thoas 405.74 2.94x
jsone 273.00 4.37x
jsx 97.47 12.23x
Pokedex
Name IPS Slower
jiffy 1669.42  
euneus 644.19 2.59x
Jason 628.42 2.66x
thoas 613.91 2.72x
jsone 322.22 5.18x
jsx 108.10 15.44x
UTF-8 unescaped
Name IPS Slower
jiffy 14.32 K  
Jason 10.11 K 1.42x
euneus 9.40 K 1.52x
thoas 9.07 K 1.58x
jsx 4.76 K 3.01x
jsone 1.83 K 7.83x

All benchmark details are available here.

Encoding with empty map as option

This benchmark passes the input and options parsed as the euneus:encode_parsed/2 function arguments. There is no option set, just an empty map, so all the default options are used. This function it's a bit slower than the smart one, but all options are analyzed in the run.

Blockchain
Name IPS Slower
jiffy 11.13 K  
Jason 4.62 K 2.41x
euneus 3.92 K 2.84x
thoas 3.74 K 2.98x
jsone 2.55 K 4.37x
jsx 1.01 K 11.03x
Giphy
Name IPS Slower
jiffy 1163.05  
Jason 416.91 2.79x
thoas 396.88 2.93x
euneus 335.54 3.47x
jsone 212.68 5.47x
jsx 79.30 14.67x
GitHub
Name IPS Slower
jiffy 3.50 K  
Jason 1.50 K 2.34x
thoas 1.28 K 2.74x
euneus 1.25 K 2.81x
jsone 0.66 K 5.31x
jsx 0.199 K 17.63x
GovTrack
Name IPS Slower
jiffy 51.97  
Jason 17.96 2.89x
euneus 17.40 2.99x
thoas 16.46 3.16x
jsone 8.54 6.09x
jsx 3.08 16.9x
Issue 90
Name IPS Slower
jiffy 36.68  
Jason 27.35 1.34x
euneus 24.04 1.53x
thoas 16.94 2.17x
jsone 16.68 2.2x
jsx 8.76 4.19x
JSON Generator
Name IPS Slower
jiffy 1177.53  
euneus 409.80 2.87x
Jason 406.64 2.9x
thoas 399.93 2.94x
jsone 270.72 4.35x
jsx 97.89 12.03x
Pokedex
Name IPS Slower
jiffy 1659.13  
Jason 620.01 2.68x
thoas 609.24 2.72x
euneus 487.64 3.4x
jsone 321.06 5.17x
jsx 108.35 15.31x
UTF-8 unescaped
Name IPS Slower
jiffy 14.36 K  
Jason 10.07 K 1.43x
euneus 9.27 K 1.55x
thoas 9.07 K 1.58x
jsx 4.78 K 3.0x
jsone 1.83 K 7.86x

All benchmark details are available here.

Encoding with all built-in plugins

This benchmark passes all the encode built-in plugins to the plugins option:

euneus:parse_encode_opts(#{
  plugins => [
    datetime,
    inet,
    pid,
    port,
    proplist,
    reference,
    timestamp
  ]
}).

It's the slowest euneus encode run, but at the same time it is very efficient.

Blockchain
Name IPS Slower
jiffy 11.28 K  
Jason 4.59 K 2.46x
euneus 3.79 K 2.97x
thoas 3.79 K 2.98x
jsone 2.54 K 4.44x
jsx 1.01 K 11.2x
Giphy
Name IPS Slower
jiffy 1172.88  
Jason 424.49 2.76x
thoas 402.26 2.92x
euneus 332.49 3.53x
jsone 213.21 5.5x
jsx 79.30 14.79x
GitHub
Name IPS Slower
jiffy 3.26 K  
Jason 1.52 K 2.14x
thoas 1.30 K 2.51x
euneus 1.22 K 2.67x
jsone 0.66 K 4.93x
jsx 0.196 K 16.62x
GovTrack
Name IPS Slower
jiffy 52.16  
Jason 18.21 2.86x
thoas 16.95 3.08x
euneus 16.66 3.13x
jsone 8.55 6.1x
jsx 3.10 16.83x
Issue 90
Name IPS Slower
jiffy 36.70  
Jason 27.43 1.34x
euneus 24.12 1.52x
thoas 16.98 2.16x
jsone 16.66 2.2x
jsx 8.78 4.18x
JSON Generator
Name IPS Slower
jiffy 1188.50  
Jason 412.20 2.88x
thoas 405.50 2.93x
euneus 397.61 2.99x
jsone 272.44 4.36x
jsx 94.44 12.59x
Pokedex
Name IPS Slower
jiffy 1648.51  
Jason 628.07 2.62x
thoas 613.82 2.69x
euneus 470.18 3.51x
jsone 321.84 5.12x
jsx 108.45 15.2x
UTF-8 unescaped
Name IPS Slower
jiffy 14.38 K  
Jason 10.09 K 1.42x
euneus 9.41 K 1.53x
thoas 9.13 K 1.57x
jsx 4.76 K 3.02x
jsone 1.79 K 8.02x

All benchmark details are available here.

Decode

Smart decoding

This benchmark uses the decode smart module via the euneus:decode/1 function. Smart modules only receive the input as the argument, so no option is available to set. Smart modules are the fastest euneus modules.

Blockchain
Name IPS Slower
jiffy 5.94 K  
euneus 5.77 K 1.03x
Jason 5.53 K 1.08x
thoas 4.87 K 1.22x
jsone 4.26 K 1.39x
jsx 1.83 K 3.25x
Giphy
Name IPS Slower
jiffy 925.19  
thoas 498.15 1.86x
euneus 494.10 1.87x
Jason 451.81 2.05x
jsone 278.02 3.33x
jsx 162.97 5.68x
GitHub
Name IPS Slower
jiffy 2.69 K  
euneus 2.09 K 1.29x
Jason 2.01 K 1.34x
thoas 1.81 K 1.49x
jsone 1.42 K 1.9x
jsx 0.51 K 5.24x
GovTrack
Name IPS Slower
jiffy 27.04  
Jason 17.02 1.59x
euneus 16.79 1.61x
thoas 15.69 1.72x
jsone 9.35 2.89x
jsx 4.40 6.14x
Issue 90
Name IPS Slower
jiffy 49.92  
euneus 26.62 1.88x
Jason 25.25 1.98x
jsone 23.25 2.15x
thoas 17.79 2.81x
jsx 9.92 5.03x
JSON Generator (Pretty)
Name IPS Slower
jiffy 1143.30  
euneus 707.45 1.62x
Jason 696.88 1.64x
thoas 624.07 1.83x
jsone 435.82 2.62x
jsx 185.81 6.15x
JSON Generator
Name IPS Slower
jiffy 698.94  
euneus 616.18 1.13x
Jason 591.72 1.18x
thoas 539.94 1.29x
jsone 400.04 1.75x
jsx 176.01 3.97x
Pokedex
Name IPS Slower
jiffy 1.36 K  
euneus 1.17 K 1.16x
thoas 1.15 K 1.18x
Jason 1.04 K 1.3x
jsone 0.59 K 2.31x
jsx 0.26 K 5.31x
UTF-8 escaped
Name IPS Slower
jiffy 10.41 K  
thoas 1.70 K 6.14x
Jason 1.69 K 6.18x
euneus 1.47 K 7.09x
jsone 1.29 K 8.09x
jsx 1.16 K 8.97x
UTF-8 unescaped
Name IPS Slower
jiffy 18.12 K  
euneus 10.44 K 1.74x
Jason 10.19 K 1.78x
thoas 9.60 K 1.89x
jsone 9.44 K 1.92x
jsx 6.57 K 2.76x

All benchmark details are available here.

Decoding with empty map as option

This benchmark passes the input and options parsed as the euneus:decode_parsed/2 function arguments. There is no option set, just an empty map, so all the default options are used. This function it's a bit slower than the smart one, but all options are analyzed in the run.

Blockchain
Name IPS Slower
jiffy 5.98 K  
Jason 5.55 K 1.08x
thoas 4.87 K 1.23x
jsone 4.28 K 1.4x
euneus 3.59 K 1.67x
jsx 1.83 K 3.26x
Giphy
Name IPS Slower
jiffy 990.34  
thoas 497.98 1.99x
Jason 456.78 2.17x
euneus 278.42 3.56x
jsone 277.71 3.57x
jsx 167.26 5.92x
GitHub
Name IPS Slower
jiffy 2.70 K  
Jason 2.01 K 1.35x
thoas 1.82 K 1.49x
jsone 1.42 K 1.91x
euneus 1.17 K 2.3x
jsx 0.52 K 5.2x
GovTrack
Name IPS Slower
jiffy 27.25  
Jason 17.27 1.58x
thoas 15.78 1.73x
jsone 9.55 2.85x
euneus 7.82 3.49x
jsx 4.45 6.13x
Issue 90
Name IPS Slower
jiffy 49.74  
Jason 25.26 1.97x
euneus 25.23 1.97x
jsone 23.29 2.14x
thoas 17.98 2.77x
jsx 9.97 4.99x
JSON Generator (Pretty)
Name IPS Slower
jiffy 1162.25  
Jason 694.53 1.67x
thoas 625.05 1.86x
jsone 436.44 2.66x
euneus 347.69 3.34x
jsx 191.09 6.08x
JSON Generator
Name IPS Slower
jiffy 696.27  
Jason 586.85 1.19x
thoas 541.03 1.29x
jsone 400.67 1.74x
euneus 317.16 2.2x
jsx 181.31 3.84x
Pokedex
Name IPS Slower
jiffy 1.35 K  
thoas 1.15 K 1.18x
Jason 1.04 K 1.3x
jsone 0.59 K 2.29x
euneus 0.40 K 3.34x
jsx 0.26 K 5.27x
UTF-8 escaped
Name IPS Slower
jiffy 10.38 K  
Jason 1.70 K 6.09x
thoas 1.70 K 6.11x
euneus 1.45 K 7.18x
jsone 1.30 K 7.97x
jsx 1.18 K 8.83x
UTF-8 unescaped
Name IPS Slower
jiffy 18.07 K  
euneus 10.37 K 1.74x
Jason 10.20 K 1.77x
thoas 9.59 K 1.89x
jsone 9.51 K 1.9x
jsx 6.58 K 2.75x

All benchmark details are available here.

Decoding with all built-in plugins

This benchmark passes all the decode built-in plugins to the plugins option:

euneus:parse_decode_opts(#{
  plugins => [
    datetime,
    timestamp,
    pid,
    port,
    reference,
    inet
  ]
}).

[!NOTE]

The proplist plugin is only available for encoding.

It's the slowest euneus decode run, but at the same time it is very efficient.

Blockchain
Name IPS Slower
jiffy 5.98 K  
Jason 5.55 K 1.08x
thoas 4.87 K 1.23x
jsone 4.28 K 1.4x
euneus 3.59 K 1.67x
jsx 1.83 K 3.26x
Giphy
Name IPS Slower
jiffy 990.34  
thoas 497.98 1.99x
Jason 456.78 2.17x
euneus 278.42 3.56x
jsone 277.71 3.57x
jsx 167.26 5.92x
GitHub
Name IPS Slower
jiffy 2.70 K  
Jason 2.01 K 1.35x
thoas 1.82 K 1.49x
jsone 1.42 K 1.91x
euneus 1.17 K 2.3x
jsx 0.52 K 5.2x
GovTrack
Name IPS Slower
jiffy 27.25  
Jason 17.27 1.58x
thoas 15.78 1.73x
jsone 9.55 2.85x
euneus 7.82 3.49x
jsx 4.45 6.13x
Issue 90
Name IPS Slower
jiffy 49.74  
Jason 25.26 1.97x
euneus 25.23 1.97x
jsone 23.29 2.14x
thoas 17.98 2.77x
jsx 9.97 4.99x
JSON Generator (Pretty)
Name IPS Slower
jiffy 1162.25  
Jason 694.53 1.67x
thoas 625.05 1.86x
jsone 436.44 2.66x
euneus 347.69 3.34x
jsx 191.09 6.08x
JSON Generator
Name IPS Slower
jiffy 696.27  
Jason 586.85 1.19x
thoas 541.03 1.29x
jsone 400.67 1.74x
euneus 317.16 2.2x
jsx 181.31 3.84x
Pokedex
Name IPS Slower
jiffy 1.35 K  
thoas 1.15 K 1.18x
Jason 1.04 K 1.3x
jsone 0.59 K 2.29x
euneus 0.40 K 3.34x
jsx 0.26 K 5.27x
UTF-8 escaped
Name IPS Slower
jiffy 10.38 K  
Jason 1.70 K 6.09x
thoas 1.70 K 6.11x
euneus 1.45 K 7.18x
jsone 1.30 K 7.97x
jsx 1.18 K 8.83x
UTF-8 unescaped
Name IPS Slower
jiffy 18.07 K  
euneus 10.37 K 1.74x
Jason 10.20 K 1.77x
thoas 9.59 K 1.89x
jsone 9.51 K 1.9x
jsx 6.58 K 2.75x

All benchmark details are available here.

Tests

There are Eunit tests in euneus_encoder and euneus_decoder and tests suites in a specific project under the euneus_test directory. Euneus has more than 330 tests.

Also, the parser is tested using JSONTestSuite and all tests passes:

JSON Test Suite

See the Euneus parser in JSONTestSuite.

[!NOTE]

All of the JSONTestSuite tests are embedded in Euneus tests.

Smart modules

Euneus has modules that permit customizations and others that use the default options. The modules without customizations are called smart. The smart versions are faster because they do not do any option checks.

If you are good to go with the default options, please use the smart versions:

Credits

Euneus is a rewrite of Thoas, so all credits go to Michał Muskała, Louis Pilfold, also both Jason and Thoas contributors. Thanks for the hard work!

Why the name Euneus?

Euneus is the twin brother of Thoas.

Sponsors

If you like this tool, please consider sponsoring me. I'm thankful for your never-ending support :heart:

I also accept coffees :coffee:

"Buy Me A Coffee"

Contributing

Issues

Feel free to submit an issue on Github.

Installation

# Clone this repo
git clone git@github.com:williamthome/euneus.git

# Navigate to the project root
cd euneus

# Compile (ensure you have rebar3 installed)
rebar3 compile

Commands

# Benchmark euneus:encode/1
$ make bench.encode
# Benchmark euneus:decode/1
$ make bench.decode
# Run all tests
$ make test
# Run all tests and dialyzer
$ make check

[!NOTE]

Open the Makefile to see all commands.

License

Euneus is released under the Apache License 2.0.

Euneus is based on Thoas, which is also Apache 2.0 licensed.

Some elements have their origins in the Poison library and were initially licensed under CC0-1.0.