Skip to content

williamthome/euneus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Euneus

An incredibly flexible and performant JSON parser, generator and formatter in pure Erlang.

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.2.2"}]}

Elixir

# mix.exs
def deps do
  [{:euneus, "~> 1.2"}]
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}
#{foo => bar, baz => undefined} #{plugins => [drop_nulls]} {"foo":"bar"} #{} #{<<"foo">> => <<"bar">>}
[{foo, bar}, {baz, undefined}] #{plugins => [drop_nulls, proplist]} {"foo":"bar"} #{} #{<<"foo">> => <<"bar">>}
#{foo => bar, baz => undefined, fizz => nil} #{nulls => [undefined, nil], plugins => [drop_nulls]} {"foo":"bar"} #{} #{<<"foo">> => <<"bar">>}
{myrecord, val} #{unhandled_encoder => fun({myrecord, Val}, Opts) ->
euneus_encoder:encode_list([myrecord, #{key => Val}], Opts)

end})
["myrecord", {"key":"val"}] #{arrays => fun([<<"myrecord">>, #{<<"key">> := Val}], _Opts) ->
{myrecord, binary_to_atom(Val)}
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() 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}}

drop_nulls

Remove keys from maps whose terms are members of the encode 'nulls' option, for example:

1> {ok, JSON} = euneus:encode_to_binary(#{a => 1, b => undefined}, #{plugins => [drop_nulls]}).
{ok,<<"{\"a\":1}">>}

1> {ok, JSON} = euneus:encode_to_binary(#{a => 1, b => undefined, c => foo}, #{nulls => [undefined, foo], plugins => [drop_nulls]}).
{ok,<<"{\"a\":1}">>}

Important

The drop_nulls plugin also works for proplists.

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.

Format

Minify

Remove extra spaces and line feeds from JSON, e.g.:

1> euneus:minify_to_binary(<<"{\n  \"foo\": \"bar\",\n  \"baz\": {\n    \"foo\": \"bar\",\n    \"0\": [\n      \"foo\",\n      0\n    ]\n  }\n}">>).
<<"{\"foo\":\"bar\",\"baz\":{\"foo\":\"bar\",\"0\":[\"foo\",0]}}">>

Prettify

Format JSON for printing, e.g.:

1> io:format("~s~n", [euneus:prettify(<<"{\"foo\":\"bar\",\"baz\":{\"foo\":\"bar\",\"0\":[\"foo\",0]}}">>)]).
{
  "foo": "bar",
  "baz": {
    "foo": "bar",
    "0": [
      "foo",
      0
    ]
  }
}

Custom

Use euneus:format/2 or euneus:format_parsed/2 for custom formatting, e.g.:

1> Opts = #{spaces => <<$\t>>, indent => <<$\t, $\t>>, crlf => <<$\n>>}.

2> io:format("~s~n", [euneus:format(<<"{\"foo\":\"bar\",\"baz\":{\"foo\":\"bar\",\"0\":[\"foo\",0]}}">>, Opts)]).
{
                "foo":  "bar",
                "baz":  {
                                "foo":  "bar",
                                "0":    [
                                                "foo",
                                                0
                                ]
                }
}

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 Giphy GitHub GovTrack Issue 90 JSON Generator Pokedex UTF-8 unescaped
Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower
jiffy 11.14 K jiffy 1162.06 jiffy 3.55 K jiffy 52.04 jiffy 36.73 jiffy 1191.94 jiffy 1669.42 jiffy 14.32 K
Jason 4.60 K 2.42x Jason 429.19 2.71x Jason 1.51 K 2.35x Jason 17.91 2.91x Jason 27.44 1.34x euneus 417.78 2.85x euneus 644.19 2.59x Jason 10.11 K 1.42x
euneus 4.49 K 2.48x euneus 427.23 2.72x euneus 1.45 K 2.44x thoas 16.56 3.14x euneus 23.24 1.58x Jason 411.69 2.9x Jason 628.42 2.66x euneus 9.40 K 1.52x
thoas 3.78 K 2.94x thoas 403.55 2.88x thoas 1.30 K 2.72x euneus 14.72 3.53x thoas 17.01 2.16x thoas 405.74 2.94x thoas 613.91 2.72x thoas 9.07 K 1.58x
jsone 2.54 K 4.39x jsone 213.42 5.44x jsone 0.67 K 5.34x jsone 8.58 6.06x jsone 16.65 2.21x jsone 273.00 4.37x jsone 322.22 5.18x jsx 4.76 K 3.01x
jsx 1.00 K 11.18x jsx 78.73 14.76x jsx 0.197 K 18.01x jsx 3.07 16.95x jsx 8.75 4.2x jsx 97.47 12.23x jsx 108.10 15.44x 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 Giphy GitHub GovTrack Issue 90 JSON Generator Pokedex UTF-8 unescaped
Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower
jiffy 11.13 K jiffy 1163.05 jiffy 3.50 K jiffy 51.97 jiffy 36.68 jiffy 1177.53 jiffy 1659.13 jiffy 14.36 K
Jason 4.62 K 2.41x Jason 416.91 2.79x Jason 1.50 K 2.34x Jason 17.96 2.89x Jason 27.35 1.34x euneus 409.80 2.87x Jason 620.01 2.68x Jason 10.07 K 1.43x
euneus 3.92 K 2.84x thoas 396.88 2.93x thoas 1.28 K 2.74x euneus 17.40 2.99x euneus 24.04 1.53x Jason 406.64 2.9x thoas 609.24 2.72x euneus 9.27 K 1.55x
thoas 3.74 K 2.98x euneus 335.54 3.47x euneus 1.25 K 2.81x thoas 16.46 3.16x thoas 16.94 2.17x thoas 399.93 2.94x euneus 487.64 3.4x thoas 9.07 K 1.58x
jsone 2.55 K 4.37x jsone 212.68 5.47x jsone 0.66 K 5.31x jsone 8.54 6.09x jsone 16.68 2.2x jsone 270.72 4.35x jsone 321.06 5.17x jsx 4.78 K 3.0x
jsx 1.01 K 11.03x jsx 79.30 14.67x jsx 0.199 K 17.63x jsx 3.08 16.9x jsx 8.76 4.19x jsx 97.89 12.03x jsx 108.35 15.31x 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
  ]
}).

Important

The drop_nulls plugin was introduced in v1.2.0 and was not included yet in the built-in plugins benchmarks.

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

Blockchain Giphy GitHub GovTrack Issue 90 JSON Generator Pokedex UTF-8 unescaped
Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower
jiffy 11.28 K jiffy 1172.88 jiffy 3.26 K jiffy 52.16 jiffy 36.70 jiffy 1188.50 jiffy 1648.51 jiffy 14.38 K
Jason 4.59 K 2.46x Jason 424.49 2.76x Jason 1.52 K 2.14x Jason 18.21 2.86x Jason 27.43 1.34x Jason 412.20 2.88x Jason 628.07 2.62x Jason 10.09 K 1.42x
euneus 3.79 K 2.97x thoas 402.26 2.92x thoas 1.30 K 2.51x thoas 16.95 3.08x euneus 24.12 1.52x thoas 405.50 2.93x thoas 613.82 2.69x euneus 9.41 K 1.53x
thoas 3.79 K 2.98x euneus 332.49 3.53x euneus 1.22 K 2.67x euneus 16.66 3.13x thoas 16.98 2.16x euneus 397.61 2.99x euneus 470.18 3.51x thoas 9.13 K 1.57x
jsone 2.54 K 4.44x jsone 213.21 5.5x jsone 0.66 K 4.93x jsone 8.55 6.1x jsone 16.66 2.2x jsone 272.44 4.36x jsone 321.84 5.12x jsx 4.76 K 3.02x
jsx 1.01 K 11.2x jsx 79.30 14.79x jsx 0.196 K 16.62x jsx 3.10 16.83x jsx 8.78 4.18x jsx 94.44 12.59x jsx 108.45 15.2x 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 Giphy GitHub GovTrack Issue 90 JSON Generator (Pretty) JSON Generator Pokedex UTF-8 escaped UTF-8 unescaped
Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower
jiffy 5.94 K jiffy 925.19 jiffy 2.69 K jiffy 27.04 jiffy 49.92 jiffy 1143.30 jiffy 698.94 jiffy 1.36 K jiffy 10.41 K jiffy 18.12 K
euneus 5.77 K 1.03x thoas 498.15 1.86x euneus 2.09 K 1.29x Jason 17.02 1.59x euneus 26.62 1.88x euneus 707.45 1.62x euneus 616.18 1.13x euneus 1.17 K 1.16x thoas 1.70 K 6.14x euneus 10.44 K 1.74x
Jason 5.53 K 1.08x euneus 494.10 1.87x Jason 2.01 K 1.34x euneus 16.79 1.61x Jason 25.25 1.98x Jason 696.88 1.64x Jason 591.72 1.18x thoas 1.15 K 1.18x Jason 1.69 K 6.18x Jason 10.19 K 1.78x
thoas 4.87 K 1.22x Jason 451.81 2.05x thoas 1.81 K 1.49x thoas 15.69 1.72x jsone 23.25 2.15x thoas 624.07 1.83x thoas 539.94 1.29x Jason 1.04 K 1.3x euneus 1.47 K 7.09x thoas 9.60 K 1.89x
jsone 4.26 K 1.39x jsone 278.02 3.33x jsone 1.42 K 1.9x jsone 9.35 2.89x thoas 17.79 2.81x jsone 435.82 2.62x jsone 400.04 1.75x jsone 0.59 K 2.31x jsone 1.29 K 8.09x jsone 9.44 K 1.92x
jsx 1.83 K 3.25x jsx 162.97 5.68x jsx 0.51 K 5.24x jsx 4.40 6.14x jsx 9.92 5.03x jsx 185.81 6.15x jsx 176.01 3.97x jsx 0.26 K 5.31x jsx 1.16 K 8.97x 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 Giphy GitHub GovTrack Issue 90 JSON Generator (Pretty) JSON Generator Pokedex UTF-8 escaped UTF-8 unescaped
Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower
jiffy 5.95 K jiffy 969.77 jiffy 2.68 K jiffy 27.26 jiffy 49.90 jiffy 1121.63 jiffy 690.56 jiffy 1.31 K jiffy 10.41 K jiffy 18.10 K
Jason 5.58 K 1.07x euneus 498.92 1.94x Jason 2.00 K 1.34x Jason 16.96 1.61x Jason 25.29 1.97x euneus 693.50 1.62x Jason 589.37 1.17x thoas 1.16 K 1.13x thoas 1.72 K 6.06x euneus 10.22 K 1.77x
euneus 5.44 K 1.09x thoas 496.53 1.95x euneus 1.94 K 1.38x euneus 15.93 1.71x euneus 24.88 2.01x Jason 692.92 1.62x euneus 560.86 1.23x euneus 1.08 K 1.21x Jason 1.71 K 6.09x Jason 10.10 K 1.79x
thoas 4.89 K 1.22x Jason 462.17 2.1x thoas 1.80 K 1.49x thoas 15.71 1.74x jsone 23.14 2.16x thoas 627.27 1.79x thoas 544.72 1.27x Jason 1.04 K 1.26x euneus 1.44 K 7.25x thoas 9.62 K 1.88x
jsone 3.95 K 1.51x jsone 255.41 3.8x jsone 1.29 K 2.07x jsone 8.91 3.06x thoas 17.76 2.81x jsone 401.43 2.79x jsone 370.06 1.87x jsone 0.53 K 2.46x jsone 1.29 K 8.07x jsone 9.42 K 1.92x
jsx 1.79 K 3.33x jsx 162.97 5.95x jsx 0.51 K 5.28x jsx 4.34 6.29x jsx 10.07 4.95x jsx 186.60 6.01x jsx 176.95 3.9x jsx 0.25 K 5.26x jsx 1.18 K 8.85x jsx 6.57 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 Giphy GitHub GovTrack Issue 90 JSON Generator (Pretty) JSON Generator Pokedex UTF-8 escaped UTF-8 unescaped
Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower Name IPS Slower
jiffy 5.98 K jiffy 990.34 jiffy 2.70 K jiffy 27.25 jiffy 49.74 jiffy 1162.25 jiffy 696.27 jiffy 1.35 K jiffy 10.38 K jiffy 18.07 K
Jason 5.55 K 1.08x thoas 497.98 1.99x Jason 2.01 K 1.35x Jason 17.27 1.58x Jason 25.26 1.97x Jason 694.53 1.67x Jason 586.85 1.19x thoas 1.15 K 1.18x Jason 1.70 K 6.09x euneus 10.37 K 1.74x
thoas 4.87 K 1.23x Jason 456.78 2.17x thoas 1.82 K 1.49x thoas 15.78 1.73x euneus 25.23 1.97x thoas 625.05 1.86x thoas 541.03 1.29x Jason 1.04 K 1.3x thoas 1.70 K 6.11x Jason 10.20 K 1.77x
jsone 4.28 K 1.4x euneus 278.42 3.56x jsone 1.42 K 1.91x jsone 9.55 2.85x jsone 23.29 2.14x jsone 436.44 2.66x jsone 400.67 1.74x jsone 0.59 K 2.29x euneus 1.45 K 7.18x thoas 9.59 K 1.89x
euneus 3.59 K 1.67x jsone 277.71 3.57x euneus 1.17 K 2.3x euneus 7.82 3.49x thoas 17.98 2.77x euneus 347.69 3.34x euneus 317.16 2.2x euneus 0.40 K 3.34x jsone 1.30 K 7.97x jsone 9.51 K 1.9x
jsx 1.83 K 3.26x jsx 167.26 5.92x jsx 0.52 K 5.2x jsx 4.45 6.13x jsx 9.97 4.99x jsx 191.09 6.08x jsx 181.31 3.84x jsx 0.26 K 5.27x jsx 1.18 K 8.83x 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:

  • Encode:
    • euneus:encode/1 or euneus_encoder_smart_json:encode/1;
    • euneus:encode_js/1 or euneus_encoder_smart_javascript:encode/1;
    • euneus:encode_html/1 or euneus_encoder_smart_html:encode/1;
    • euneus:encode_unicode/1 or euneus_encoder_smart_unicode:encode/1;
  • Decode:
    • euneus:decode/1 or euneus_decoder_smart:decode/1.

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 ❤️

I also accept coffees ☕

"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.