diff --git a/c_src/encoder.c b/c_src/encoder.c index 2b67273..fcacf81 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -580,6 +580,7 @@ enc_object_element(Encoder* e, int first, ERL_NIF_TERM curr, ERL_NIF_TERM* stack if(first && !enc_start_object(e)) { return enc_error(e, "internal_error"); } +next_object_elt: if(enif_is_empty_list(env, curr)) { if(!enc_end_object(e)) { return enc_error(e, "internal_error"); @@ -595,6 +596,22 @@ enc_object_element(Encoder* e, int first, ERL_NIF_TERM curr, ERL_NIF_TERM* stack if(arity != 2) { return enc_obj_error(e, "invalid_object_member_arity", item); } + if(enif_compare(tuple[0], e->atoms->atom_partial_object) == 0) { + ErlNifBinary bin; + if(!enif_inspect_binary(env, tuple[1], &bin)) { + return enc_error(e, "internal_error"); + } + if(bin.size > 0) { + if(!first && !enc_comma(e)) { + return enc_error(e, "internal_error"); + } + if(!enc_unknown(e, tuple[1])) { + return enc_error(e, "internal_error"); + } + first = 0; + } + goto next_object_elt; + } if(!first && !enc_comma(e)) { return enc_error(e, "internal_error"); } @@ -617,10 +634,14 @@ enc_array_element(Encoder* e, int first, ERL_NIF_TERM curr, ERL_NIF_TERM* stackp ErlNifEnv* env = e->env; ERL_NIF_TERM stack = *stackp; ERL_NIF_TERM item; + const ERL_NIF_TERM* tuple; + int arity; + ErlNifBinary bin; if(first && !enc_start_array(e)) { return enc_error(e, "internal_error"); } +next_array_elt: if(enif_is_empty_list(env, curr)) { if(!enc_end_array(e)) { return enc_error(e, "internal_error"); @@ -630,6 +651,21 @@ enc_array_element(Encoder* e, int first, ERL_NIF_TERM curr, ERL_NIF_TERM* stackp if(!enif_get_list_cell(env, curr, &item, &curr)) { return enc_error(e, "internal_error"); } + if(enif_get_tuple(env, item, &arity, &tuple) && + (arity == 2) && + (enif_compare(tuple[0], e->atoms->atom_partial_array) == 0) && + enif_inspect_binary(env, tuple[1], &bin)) { + if (bin.size > 0) { + if(!first && !enc_comma(e)) { + return enc_error(e, "internal_error"); + } + if(!enc_unknown(e, tuple[1])) { + return enc_error(e, "internal_error"); + } + first = 0; + } + goto next_array_elt; + } if(!first && !enc_comma(e)) { return enc_error(e, "internal_error"); } diff --git a/c_src/jiffy.c b/c_src/jiffy.c index 03ded3e..1178aaf 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -16,6 +16,8 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_null = make_atom(env, "null"); st->atom_true = make_atom(env, "true"); st->atom_false = make_atom(env, "false"); + st->atom_partial_object = make_atom(env, "$partial_object$"); + st->atom_partial_array = make_atom(env, "$partial_array$"); st->atom_bignum = make_atom(env, "bignum"); st->atom_bignum_e = make_atom(env, "bignum_e"); st->atom_bigdbl = make_atom(env, "bigdbl"); diff --git a/c_src/jiffy.h b/c_src/jiffy.h index ef03a06..d78a7ff 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -19,6 +19,8 @@ typedef struct { ERL_NIF_TERM atom_null; ERL_NIF_TERM atom_true; ERL_NIF_TERM atom_false; + ERL_NIF_TERM atom_partial_object; + ERL_NIF_TERM atom_partial_array; ERL_NIF_TERM atom_bignum; ERL_NIF_TERM atom_bignum_e; ERL_NIF_TERM atom_bigdbl; diff --git a/src/jiffy.erl b/src/jiffy.erl index 88c1925..c807ef8 100644 --- a/src/jiffy.erl +++ b/src/jiffy.erl @@ -2,7 +2,7 @@ % See the LICENSE file for more information. -module(jiffy). --export([decode/1, decode/2, encode/1, encode/2]). +-export([decode/1, decode/2, encode/1, encode/2, partial_encode/2]). -define(NOT_LOADED, not_loaded(?LINE)). -compile([no_native]). @@ -19,17 +19,20 @@ | json_array() | json_preencoded(). --type json_array() :: [json_value()]. +-type json_array() :: [json_value()|json_partial_array()]. -type json_string() :: atom() | binary(). -type json_number() :: integer() | float(). +-type json_partial_array() :: {'$partial_array$', iodata()}. +-type json_partial_object() :: {'$partial_object$', iodata()}. + -ifdef(JIFFY_NO_MAPS). --type json_object() :: {[{json_string(),json_value()}]}. +-type json_object() :: {[({json_string(),json_value()})|json_partial_object()]}. -else. --type json_object() :: {[{json_string(),json_value()}]} +-type json_object() :: {[({json_string(),json_value()})|json_partial_object()]} | #{json_string() => json_value()}. -endif. @@ -109,6 +112,16 @@ encode(Data, Options) -> end. +-spec partial_encode(json_array(), encode_options()) -> json_partial_array(); + (json_object(), encode_options()) -> json_partial_object(). +partial_encode(Data, Options) when is_list(Data) -> + Json = iolist_to_binary(encode(Data, Options)), + {'$partial_array$', binary_part(Json, 1, byte_size(Json) - 2)}; +partial_encode(Data, Options) when is_tuple(Data) -> + Json = iolist_to_binary(encode(Data, Options)), + {'$partial_object$', binary_part(Json, 1, byte_size(Json) - 2)}. + + finish_decode({bignum, Value}) -> list_to_integer(binary_to_list(Value)); finish_decode({bignum_e, Value}) -> diff --git a/test/jiffy_18_preencode_tests.erl b/test/jiffy_18_preencode_tests.erl index 5bc36bf..950c726 100644 --- a/test/jiffy_18_preencode_tests.erl +++ b/test/jiffy_18_preencode_tests.erl @@ -17,6 +17,11 @@ gen(ok, {E1, J, E2}) -> {msg("~p", [E1]), [ {"Encode", ?_assertEqual(J, enc(E1))}, {"Decode", ?_assertEqual(E2, dec(J))} + ]}; + +gen(ok, {E, J}) -> + {msg("~p", [E]), [ + {"Encode", ?_assertEqual(J, enc(E))} ]}. %% gen(error, E) -> @@ -45,7 +50,46 @@ cases(ok) -> , { [ {json, JSON}, {json, JSON} ], <<"[[1,\"a\"],[1,\"a\"]]">>, [ EJSON, EJSON ]} , { {[ {<<"a">>, {json, JSON}} ]}, <<"{\"a\":[1,\"a\"]}">>, {[ {<<"a">>, EJSON} ]}} ], - TopTests ++ BuriedTests. + + PartialArray1 = jiffy:partial_encode([ 2, 3 ], []), + PartialArray2 = jiffy:partial_encode([], []), + PartialArray3 = jiffy:partial_encode([ 5 ], []), + PartialArrayTests = + [ {[ PartialArray1 ], <<"[2,3]">>} + , {[ 1, PartialArray1 ], <<"[1,2,3]">>} + , {[ PartialArray1, 4 ], <<"[2,3,4]">>} + , {[ 1, PartialArray1, 4 ], <<"[1,2,3,4]">>} + , {[ PartialArray2 ], <<"[]">>} + , {[ 1, PartialArray2 ], <<"[1]">>} + , {[ PartialArray2, 4 ], <<"[4]">>} + , {[ 1, PartialArray2, 4 ], <<"[1,4]">>} + , {[ PartialArray1, PartialArray2 ], <<"[2,3]">>} + , {[ PartialArray2, PartialArray1 ], <<"[2,3]">>} + , {[ PartialArray1, PartialArray1 ], <<"[2,3,2,3]">>} + , {[ PartialArray2, PartialArray2 ], <<"[]">>} + , {[ PartialArray1, PartialArray3 ], <<"[2,3,5]">>} + , {[ 1, PartialArray1, 4, PartialArray3, 6 ], <<"[1,2,3,4,5,6]">>} + ], + + PartialObject1 = jiffy:partial_encode({[ {<<"ii">>, <<"two">>}, {<<"iii">>, 3} ]}, []), + PartialObject2 = jiffy:partial_encode({[]}, []), + PartialObject3 = jiffy:partial_encode({[ {<<"v">>, [ 1, 2, 3, 4, 5 ]} ]}, []), + PartialObjectTests = + [ {{[ PartialObject1 ]}, <<"{\"ii\":\"two\",\"iii\":3}">>} + , {{[ {<<"i">>, 1}, PartialObject1 ]}, <<"{\"i\":1,\"ii\":\"two\",\"iii\":3}">>} + , {{[ PartialObject1, {<<"iv">>, 4} ]}, <<"{\"ii\":\"two\",\"iii\":3,\"iv\":4}">>} + , {{[ {<<"i">>, 1}, PartialObject1, {<<"iv">>, 4} ]}, <<"{\"i\":1,\"ii\":\"two\",\"iii\":3,\"iv\":4}">>} + , {{[ PartialObject2 ]}, <<"{}">>} + , {{[ {<<"i">>, 1}, PartialObject2 ]}, <<"{\"i\":1}">>} + , {{[ PartialObject2, {<<"iv">>, 4} ]}, <<"{\"iv\":4}">>} + , {{[ {<<"i">>, 1}, PartialObject2, {<<"iv">>, 4} ]}, <<"{\"i\":1,\"iv\":4}">>} + , {{[ PartialObject1, PartialObject2 ]}, <<"{\"ii\":\"two\",\"iii\":3}">>} + , {{[ PartialObject2, PartialObject1 ]}, <<"{\"ii\":\"two\",\"iii\":3}">>} + , {{[ PartialObject2, PartialObject2 ]}, <<"{}">>} + , {{[ PartialObject1, PartialObject3 ]}, <<"{\"ii\":\"two\",\"iii\":3,\"v\":[1,2,3,4,5]}">>} + ], + + TopTests ++ BuriedTests ++ PartialArrayTests ++ PartialObjectTests. %% cases(error) -> %% [ {json, true}