diff --git a/README.md b/README.md index e28e2e8f..0360c499 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ The options for decode are: will ensure that the parsed object only contains a single entry containing the last value seen. This mirrors the parsing beahvior of virtually every other JSON parser. +* `attempt_atom` - Attempts to convert keys to existing atoms. Will + return keys as binaries if the atom does not exists. * `copy_strings` - Normally, when strings are decoded, they are created as sub-binaries of the input data. With some workloads, this leads to an undesirable bloating of memory: Strings in the decode @@ -91,8 +93,8 @@ The options for encode are: Data Format ----------- - Erlang JSON Erlang - ========================================================================== + Erlang JSON Erlang Notes + ====================================================================================== null -> null -> null true -> true -> true @@ -107,11 +109,17 @@ Data Format {[]} -> {} -> {[]} {[{foo, bar}]} -> {"foo": "bar"} -> {[{<<"foo">>, <<"bar">>}]} {[{<<"foo">>, <<"bar">>}]} -> {"foo": "bar"} -> {[{<<"foo">>, <<"bar">>}]} - #{<<"foo">> => <<"bar">>} -> {"foo": "bar"} -> #{<<"foo">> => <<"bar">>} + #{<<"foo">> => <<"bar">>} -> {"foo": "bar"} -> #{<<"foo">> => <<"bar">>} (1) + {[{<<"foo">>, <<"bar">>}]} -> {"foo": "bar"} -> {[{foo, <<"bar">>}]} (2) + #{<<"foo">> => <<"bar">>} -> {"foo": "bar"} -> #{foo => <<"bar">>} (1, 2) + +Note 1: This entry is only valid for VM's that support the `maps` data type + (i.e., 17.0 and newer) and client code must pass the `return_maps` + option to `jiffy:decode/2`. + +Note 2: This entry is only valid if the atom existed before and the client code must + pass the `attempt_atom` option to `jiffy:decode/2`. -N.B. The last entry in this table is only valid for VM's that support -the `maps` data type (i.e., 17.0 and newer) and client code must pass -the `return_maps` option to `jiffy:decode/2`. Improvements over EEP0018 ------------------------- diff --git a/c_src/decoder.c b/c_src/decoder.c index 8f78117c..70bcff56 100644 --- a/c_src/decoder.c +++ b/c_src/decoder.c @@ -55,6 +55,7 @@ typedef struct { int return_trailer; int dedupe_keys; int copy_strings; + int attempt_atom; ERL_NIF_TERM null_term; unsigned char* p; @@ -85,6 +86,7 @@ dec_new(ErlNifEnv* env) d->return_trailer = 0; d->dedupe_keys = 0; d->copy_strings = 0; + d->attempt_atom = 0; d->null_term = d->atoms->atom_null; d->p = NULL; @@ -695,6 +697,8 @@ decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) d->null_term = d->atoms->atom_nil; } else if(get_null_term(env, val, &(d->null_term))) { continue; + } else if(enif_is_identical(val, d->atoms->atom_attempt_atom)) { + d->attempt_atom = 1; } else { return enif_make_badarg(env); } @@ -980,7 +984,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } dec_pop_assert(d, st_value); if(!make_object(env, curr, &val, - d->return_maps, d->dedupe_keys)) { + d->return_maps, d->dedupe_keys, d->attempt_atom)) { ret = dec_error(d, "internal_object_error"); goto done; } diff --git a/c_src/jiffy.c b/c_src/jiffy.c index dfca7c70..b13da2d5 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -35,6 +35,7 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_escape_forward_slashes = make_atom(env, "escape_forward_slashes"); st->atom_dedupe_keys = make_atom(env, "dedupe_keys"); st->atom_copy_strings = make_atom(env, "copy_strings"); + st->atom_attempt_atom = make_atom(env, "attempt_atom"); // Markers used in encoding st->ref_object = make_atom(env, "$object_ref$"); diff --git a/c_src/jiffy.h b/c_src/jiffy.h index 9c97945d..806ca7eb 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -44,6 +44,7 @@ typedef struct { ERL_NIF_TERM atom_escape_forward_slashes; ERL_NIF_TERM atom_dedupe_keys; ERL_NIF_TERM atom_copy_strings; + ERL_NIF_TERM atom_attempt_atom; ERL_NIF_TERM ref_object; ERL_NIF_TERM ref_array; @@ -72,7 +73,7 @@ void dec_destroy(ErlNifEnv* env, void* obj); void enc_destroy(ErlNifEnv* env, void* obj); int make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, - int ret_map, int dedupe_keys); + int ret_map, int dedupe_keys, int attempt_atom); int int_from_hex(const unsigned char* p); int int_to_hex(int val, unsigned char* p); diff --git a/c_src/objects.cc b/c_src/objects.cc index 1a16699c..4dca879c 100644 --- a/c_src/objects.cc +++ b/c_src/objects.cc @@ -17,9 +17,23 @@ BEGIN_C +static ERL_NIF_TERM +key_attempt_atom(ErlNifEnv* env, ERL_NIF_TERM key) { + ERL_NIF_TERM keyatom; + ErlNifBinary keybin; + + if(enif_inspect_binary(env, key, &keybin) && keybin.size < 256) { + if(enif_make_existing_atom_len(env, (char *)keybin.data, keybin.size, + &keyatom, ERL_NIF_UTF8)) { + return keyatom; + } + } + return key; +} + int make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, - int ret_map, int dedupe_keys) + int ret_map, int dedupe_keys, int attempt_atom) { ERL_NIF_TERM ret; ERL_NIF_TERM key; @@ -37,6 +51,9 @@ make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, if(!enif_get_list_cell(env, pairs, &key, &pairs)) { assert(0 == 1 && "Unbalanced object pairs."); } + if(attempt_atom) { + key = key_attempt_atom(env, key); + } if(!enif_get_map_value(env, ret, key, &old_val)) { if(!enif_make_map_put(env, ret, key, val, &ret)) { return 0; @@ -53,6 +70,9 @@ make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, if(!enif_get_list_cell(env, pairs, &key, &pairs)) { assert(0 == 1 && "Unbalanced object pairs."); } + if(attempt_atom) { + key = key_attempt_atom(env, key); + } if(dedupe_keys) { ErlNifBinary bin; if(!enif_inspect_binary(env, key, &bin)) { diff --git a/src/jiffy.erl b/src/jiffy.erl index 6af15b5f..720920eb 100644 --- a/src/jiffy.erl +++ b/src/jiffy.erl @@ -41,6 +41,7 @@ | return_trailer | dedupe_keys | copy_strings + | attempt_atom | {null_term, any()} | {bytes_per_iter, non_neg_integer()} | {bytes_per_red, non_neg_integer()}. diff --git a/test/jiffy_16_attempt_atom_tests.erl b/test/jiffy_16_attempt_atom_tests.erl new file mode 100644 index 00000000..d8f58c85 --- /dev/null +++ b/test/jiffy_16_attempt_atom_tests.erl @@ -0,0 +1,32 @@ +% This file is part of Jiffy released under the MIT license. +% See the LICENSE file for more information. + +-module(jiffy_16_attempt_atom_tests). + +-include_lib("eunit/include/eunit.hrl"). + +attempt_atom_test_() -> + Opts = [attempt_atom], + _ = key_is_atom, + Cases = [ + {<<"{\"key_no_atom\":1}">>, {[{<<"key_no_atom">>, 1}]}}, + {<<"{\"key_is_atom\":1}">>, {[{key_is_atom, 1}]}} + ], + {"Test attempt_atom", lists:map(fun({Data, Result}) -> + ?_assertEqual(Result, jiffy:decode(Data, Opts)) + end, Cases)}. + +-ifndef(JIFFY_NO_MAPS). + +attempt_atom_map_test_() -> + Opts = [attempt_atom, return_maps], + _ = key_is_atom, + Cases = [ + {<<"{\"key_no_atom\":1}">>, #{<<"key_no_atom">> => 1}}, + {<<"{\"key_is_atom\":1}">>, #{key_is_atom => 1}} + ], + {"Test attempt_atom_map", lists:map(fun({Data, Result}) -> + ?_assertEqual(Result, jiffy:decode(Data, Opts)) + end, Cases)}. + +-endif.