diff --git a/c_src/decoder.c b/c_src/decoder.c index c02b18d..7cc74bd 100644 --- a/c_src/decoder.c +++ b/c_src/decoder.c @@ -51,6 +51,7 @@ typedef struct { size_t bytes_per_iter; int is_partial; + int return_maps; char* p; unsigned char* u; @@ -77,6 +78,7 @@ dec_new(ErlNifEnv* env) d->bytes_per_iter = DEFAULT_BYTES_PER_ITER; d->is_partial = 0; + d->return_maps = 0; d->p = NULL; d->u = NULL; @@ -606,11 +608,41 @@ parse: } ERL_NIF_TERM -make_object(ErlNifEnv* env, ERL_NIF_TERM pairs) +make_empty_object(ErlNifEnv* env, int ret_map) { - ERL_NIF_TERM ret = enif_make_list(env, 0); - ERL_NIF_TERM key, val; +#if MAP_TYPE_PRESENT + if(ret_map) { + return enif_make_new_map(env); + } +#endif + + return enif_make_tuple1(env, enif_make_list(env, 0)); +} + +int +make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out, int ret_map) +{ + 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(!enif_make_map_put(env, ret, key, val, &ret)) { + return 0; + } + } + *out = ret; + return 1; + } +#endif + + ret = enif_make_list(env, 0); while(enif_get_list_cell(env, pairs, &val, &pairs)) { if(!enif_get_list_cell(env, pairs, &key, &pairs)) { assert(0 == 1 && "Unbalanced object pairs."); @@ -618,8 +650,9 @@ make_object(ErlNifEnv* env, ERL_NIF_TERM pairs) val = enif_make_tuple2(env, key, val); ret = enif_make_list_cell(env, val, ret); } + *out = enif_make_tuple1(env, ret); - return enif_make_tuple1(env, ret); + return 1; } ERL_NIF_TERM @@ -668,6 +701,12 @@ decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) while(enif_get_list_cell(env, opts, &val, &opts)) { if(get_bytes_per_iter(env, val, &(d->bytes_per_iter))) { continue; + } else if(enif_compare(val, d->atoms->atom_return_maps) == 0) { +#if MAP_TYPE_PRESENT + d->return_maps = 1; +#else + return enif_make_badarg(env); +#endif } else { return enif_make_badarg(env); } @@ -862,7 +901,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) dec_pop(d, st_key); dec_pop(d, st_object); dec_pop(d, st_value); - val = enif_make_tuple1(env, curr); + val = make_empty_object(env, d->return_maps); if(!enif_get_list_cell(env, objs, &curr, &objs)) { ret = dec_error(d, "internal_error"); goto done; @@ -931,7 +970,10 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) } dec_pop(d, st_object); dec_pop(d, st_value); - val = make_object(env, curr); + if(!make_object(env, curr, &val, d->return_maps)) { + ret = dec_error(d, "internal_object_error"); + goto done; + } if(!enif_get_list_cell(env, objs, &curr, &objs)) { ret = dec_error(d, "internal_error"); goto done; diff --git a/c_src/encoder.c b/c_src/encoder.c index 86813e0..fff9752 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -500,6 +500,49 @@ enc_comma(Encoder* e) return 1; } +#if MAP_TYPE_PRESENT +int +enc_map_to_ejson(ErlNifEnv* env, ERL_NIF_TERM map, ERL_NIF_TERM* out) +{ + ErlNifMapIterator iter; + size_t size; + + ERL_NIF_TERM list; + ERL_NIF_TERM tuple; + ERL_NIF_TERM key; + ERL_NIF_TERM val; + + if(!enif_get_map_size(env, map, &size)) { + fprintf(stderr, "bad map size\r\n"); + return 0; + } + + list = enif_make_list(env, 0); + + if(size == 0) { + *out = enif_make_tuple1(env, list); + return 1; + } + + if(!enif_map_iterator_create(env, map, &iter, ERL_NIF_MAP_ITERATOR_HEAD)) { + fprintf(stderr, "bad iterator create\r\n"); + return 0; + } + + do { + if(!enif_map_iterator_get_pair(env, &iter, &key, &val)) { + fprintf(stderr, "bad get pair\r\n"); + return 0; + } + tuple = enif_make_tuple2(env, key, val); + list = enif_make_list_cell(env, tuple, list); + } while(enif_map_iterator_next(env, &iter)); + + *out = enif_make_tuple1(env, list); + return 1; +} +#endif + ERL_NIF_TERM encode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { @@ -743,6 +786,14 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) stack = enif_make_list_cell(env, curr, stack); stack = enif_make_list_cell(env, e->atoms->ref_object, stack); stack = enif_make_list_cell(env, tuple[1], stack); +#if MAP_TYPE_PRESENT + } else if(enif_is_map(env, curr)) { + if(!enc_map_to_ejson(env, curr, &curr)) { + ret = enc_error(e, "internal_error"); + goto done; + } + stack = enif_make_list_cell(env, curr, stack); +#endif } else if(enif_is_list(env, curr)) { if(!enc_start_array(e)) { ret = enc_error(e, "internal_error"); diff --git a/c_src/jiffy.c b/c_src/jiffy.c index d8b11c3..b3b4ba4 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -25,6 +25,7 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_force_utf8 = make_atom(env, "force_utf8"); st->atom_iter = make_atom(env, "iter"); st->atom_bytes_per_iter = make_atom(env, "bytes_per_iter"); + st->atom_return_maps = make_atom(env, "return_maps"); // 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 b0cc75d..90f364c 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -8,6 +8,10 @@ #define DEFAULT_BYTES_PER_ITER 2048 +#define MAP_TYPE_PRESENT \ + ((ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 6) \ + || (ERL_NIF_MAJOR_VERSION > 2)) + typedef struct { ERL_NIF_TERM atom_ok; ERL_NIF_TERM atom_error; @@ -23,6 +27,7 @@ typedef struct { ERL_NIF_TERM atom_force_utf8; ERL_NIF_TERM atom_iter; ERL_NIF_TERM atom_bytes_per_iter; + ERL_NIF_TERM atom_return_maps; ERL_NIF_TERM ref_object; ERL_NIF_TERM ref_array; diff --git a/rebar.config b/rebar.config index 67625e9..831ca38 100644 --- a/rebar.config +++ b/rebar.config @@ -25,6 +25,9 @@ {"win32", "CXXFLAGS", "-g -Wall -O3"} ]}. +{erl_opts, [ + {platform_define, "R1(1|2|3|4|5|6)", 'JIFFY_NO_MAPS'} +]}. {eunit_opts, [ verbose, diff --git a/rebar.config.script b/rebar.config.script index 58d4ff7..8a0049d 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -7,9 +7,7 @@ % % This script is based on the example provided with Rebar. -ErlOpts = {erl_opts, [ - {d, 'JIFFY_DEV'} -]}, +ErlOpts = [{d, 'JIFFY_DEV'}], Proper = [ {proper, ".*", {git, "git://github.com/manopapad/proper.git", "master"}} @@ -21,13 +19,19 @@ DevMarker = filename:join([ConfigPath, ".jiffy.dev"]), case filelib:is_file(DevMarker) of true -> % Don't override existing dependencies - NewConfig = case lists:keyfind(deps, 1, CONFIG) of + Config0 = case lists:keyfind(deps, 1, CONFIG) of false -> CONFIG ++ [{deps, Proper}]; {deps, DepsList} -> lists:keyreplace(deps, 1, CONFIG, {deps, DepsList ++ Proper}) end, - NewConfig ++ [ErlOpts]; + Config1 = case lists:keyfind(erl_opts, 1, Config0) of + false -> + Config0 ++ [{erl_opts, ErlOpts}]; + {erl_opts, Opts} -> + NewOpts = {erl_opts, Opts ++ ErlOpts}, + lists:keyreplace(erl_opts, 1, Config0, NewOpts) + end; false -> CONFIG end. diff --git a/src/jiffy.erl b/src/jiffy.erl index 26e03d1..8903831 100644 --- a/src/jiffy.erl +++ b/src/jiffy.erl @@ -67,7 +67,20 @@ finish_decode({Pairs}) when is_list(Pairs) -> finish_decode(Vals) when is_list(Vals) -> finish_decode_arr(Vals, []); finish_decode(Val) -> + maybe_map(Val). + +-ifndef(JIFFY_NO_MAPS). +maybe_map(Obj) when is_map(Obj) -> + maps:map(fun finish_decode_map/2, Obj); +maybe_map(Val) -> + Val. + +finish_decode_map(_, V) -> + finish_decode(V). +-else. +maybe_map(Val) -> Val. +-endif. finish_decode_obj([], Acc) -> {lists:reverse(Acc)}; diff --git a/src/jiffy_utf8.erl b/src/jiffy_utf8.erl index ee937fe..f36cbc7 100644 --- a/src/jiffy_utf8.erl +++ b/src/jiffy_utf8.erl @@ -12,8 +12,20 @@ fix(Values) when is_list(Values) -> fix(Bin) when is_binary(Bin) -> fix_bin(Bin); fix(Val) -> + maybe_map(Val). + +-ifndef(JIFFY_NO_MAPS). +maybe_map(Obj) when is_map(Obj) -> + maps:fold(fun fix_map/3, maps:new(), Obj); +maybe_map(Val) -> Val. +fix_map(K, V, Acc) -> + maps:put(fix(K), fix(V), Acc). +-else. +maybe_map(Val) -> + Val. +-endif. fix_props([], Acc) -> {lists:reverse(Acc)}; diff --git a/test/jiffy_tests.erl b/test/jiffy_tests.erl index 0655b0d..9ecea32 100644 --- a/test/jiffy_tests.erl +++ b/test/jiffy_tests.erl @@ -26,6 +26,24 @@ prop_encode_decode() -> end ). +-ifndef(JIFFY_NO_MAPS). +to_map_ejson({Props}) -> + NewProps = [{K, to_map_ejson(V)} || {K, V} <- Props], + maps:from_list(NewProps); +to_map_ejson(Vals) when is_list(Vals) -> + [to_map_ejson(V) || V <- Vals]; +to_map_ejson(Val) -> + Val. + +prop_map_encode_decode() -> + ?FORALL(Data, json(), + begin + MapData = to_map_ejson(Data), + MapData == jiffy:decode(jiffy:encode(MapData), [return_maps]) + end + ). +-endif. + prop_encode_decode_pretty() -> ?FORALL(Data, json(), begin