diff --git a/c_src/decoder.c b/c_src/decoder.c index 34cf467c..813ffcff 100644 --- a/c_src/decoder.c +++ b/c_src/decoder.c @@ -53,6 +53,7 @@ typedef struct { int is_partial; int return_maps; int return_trailer; + int attempt_atom; ERL_NIF_TERM null_term; char* p; @@ -82,6 +83,7 @@ dec_new(ErlNifEnv* env) d->is_partial = 0; d->return_maps = 0; d->return_trailer = 0; + d->attempt_atom = 0; d->null_term = d->atoms->atom_null; d->p = NULL; @@ -623,20 +625,41 @@ make_empty_object(ErlNifEnv* env, int ret_map) return enif_make_tuple1(env, enif_make_list(env, 0)); } +ERL_NIF_TERM +key_attempt_atom(ErlNifEnv* env, ERL_NIF_TERM key) { + ERL_NIF_TERM keyatom; + ErlNifBinary keybin; + // maximum atom length is 255 bytes + char keybuf[256]; + + if(enif_inspect_binary(env, key, &keybin) && keybin.size < sizeof(keybuf)) { + memcpy(keybuf, keybin.data, keybin.size); + keybuf[keybin.size] = 0; + if(enif_make_existing_atom(env, keybuf, &keyatom, ERL_NIF_LATIN1)) { + return keyatom; + } + } + return key; +} + int -make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, int ret_map) +make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, int ret_map, int attempt_atom) { ERL_NIF_TERM ret; ERL_NIF_TERM key; ERL_NIF_TERM val; #if MAP_TYPE_PRESENT + if(ret_map) { ret = enif_make_new_map(env); while(enif_get_list_cell(env, pairs, &val, &pairs)) { 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_make_map_put(env, ret, key, val, &ret)) { return 0; } @@ -651,6 +674,9 @@ make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, int ret_map) if(!enif_get_list_cell(env, pairs, &key, &pairs)) { assert(0 == 1 && "Unbalanced object pairs."); } + if(attempt_atom) { + key = key_attempt_atom(env, key); + } val = enif_make_tuple2(env, key, val); ret = enif_make_list_cell(env, val, ret); } @@ -720,6 +746,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_compare(val, d->atoms->atom_attempt_atom) == 0) { + d->attempt_atom = 1; } else { return enif_make_badarg(env); } @@ -984,7 +1012,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } dec_pop(d, st_object); dec_pop(d, st_value); - if(!make_object(env, curr, &val, d->return_maps)) { + if(!make_object(env, curr, &val, d->return_maps, 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 1ea60a31..96b0cd4c 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -32,6 +32,7 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_use_nil = make_atom(env, "use_nil"); st->atom_null_term = make_atom(env, "null_term"); st->atom_escape_forward_slashes = make_atom(env, "escape_forward_slashes"); + 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 abe51543..ac029829 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -35,6 +35,7 @@ typedef struct { ERL_NIF_TERM atom_use_nil; ERL_NIF_TERM atom_null_term; ERL_NIF_TERM atom_escape_forward_slashes; + ERL_NIF_TERM atom_attempt_atom; ERL_NIF_TERM ref_object; ERL_NIF_TERM ref_array; diff --git a/src/jiffy.erl b/src/jiffy.erl index 78f4cf26..1ffb9c24 100644 --- a/src/jiffy.erl +++ b/src/jiffy.erl @@ -39,6 +39,7 @@ -type decode_option() :: return_maps | use_nil | return_trailer + | 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..71d1cf5a --- /dev/null +++ b/test/jiffy_16_attempt_atom_tests.erl @@ -0,0 +1,28 @@ +% 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)}. + +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)}.