From f475d3a5bb2fe52ad9da1f8213d77e505934392b Mon Sep 17 00:00:00 2001 From: David Hull Date: Sat, 11 Mar 2017 01:47:31 +0000 Subject: [PATCH 1/4] Add preencoded JSON support. --- c_src/encoder.c | 115 ++++++++++++++++++++---------- c_src/jiffy.c | 1 + c_src/jiffy.h | 1 + src/jiffy.erl | 5 +- test/jiffy_16_preencode_tests.erl | 53 ++++++++++++++ 5 files changed, 137 insertions(+), 38 deletions(-) create mode 100644 test/jiffy_16_preencode_tests.erl diff --git a/c_src/encoder.c b/c_src/encoder.c index e864777..4d8bb49 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -400,6 +400,33 @@ enc_string(Encoder* e, ERL_NIF_TERM val) return 1; } +static inline int +enc_json(Encoder* e, ERL_NIF_TERM val) +{ + ErlNifBinary bin; + unsigned char* data; + size_t size; + + if(!enif_is_binary(e->env, val)) { + return 0; + } + if(!enif_inspect_binary(e->env, val, &bin)) { + return 0; + } + data = bin.data; + size = bin.size; + + if(!enc_ensure(e, size + 2)) { + return 0; + } + + memcpy(e->p + e->i, data, size); + e->i += size; + e->count++; + + return 1; +} + static inline int enc_long(Encoder* e, ErlNifSInt64 val) { @@ -783,48 +810,62 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) goto done; } } else if(enif_get_tuple(env, curr, &arity, &tuple)) { - if(arity != 1) { - ret = enc_obj_error(e, "invalid_ejson", curr); - goto done; - } - if(!enif_is_list(env, tuple[0])) { - ret = enc_obj_error(e, "invalid_object", curr); - goto done; - } - if(!enc_start_object(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - if(enif_is_empty_list(env, tuple[0])) { - if(!enc_end_object(e)) { + if(arity == 1) { + if(!enif_is_list(env, tuple[0])) { + ret = enc_obj_error(e, "invalid_object", curr); + goto done; + } + if(!enc_start_object(e)) { ret = enc_error(e, "internal_error"); goto done; } - continue; - } - if(!enif_get_list_cell(env, tuple[0], &item, &curr)) { - ret = enc_error(e, "internal_error"); - goto done; - } - if(!enif_get_tuple(env, item, &arity, &tuple)) { - ret = enc_obj_error(e, "invalid_object_member", item); - goto done; - } - if(arity != 2) { - ret = enc_obj_error(e, "invalid_object_member_arity", item); - goto done; - } - if(!enc_string(e, tuple[0])) { - ret = enc_obj_error(e, "invalid_object_member_key", tuple[0]); - goto done; - } - if(!enc_colon(e)) { - ret = enc_error(e, "internal_error"); + if(enif_is_empty_list(env, tuple[0])) { + if(!enc_end_object(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + continue; + } + if(!enif_get_list_cell(env, tuple[0], &item, &curr)) { + ret = enc_error(e, "internal_error"); + goto done; + } + if(!enif_get_tuple(env, item, &arity, &tuple)) { + ret = enc_obj_error(e, "invalid_object_member", item); + goto done; + } + if(arity != 2) { + ret = enc_obj_error(e, "invalid_object_member_arity", item); + goto done; + } + if(!enc_string(e, tuple[0])) { + ret = enc_obj_error(e, "invalid_object_member_key", tuple[0]); + goto done; + } + if(!enc_colon(e)) { + ret = enc_error(e, "internal_error"); + goto done; + } + 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); + } else if(arity == 2) { + if(enif_compare(tuple[0], e->atoms->atom_json) != 0) { + ret = enc_obj_error(e, "invalid_ejson", curr); + goto done; + } + if(!enif_is_binary(env, tuple[1])) { + ret = enc_obj_error(e, "invalid_json_string", curr); + goto done; + } + if(!enc_json(e, tuple[1])) { + ret = enc_error(e, "internal_error"); + goto done; + } + } else { + ret = enc_obj_error(e, "invalid_ejson", curr); goto done; } - 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)) { diff --git a/c_src/jiffy.c b/c_src/jiffy.c index 1ea60a3..1a8d285 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -16,6 +16,7 @@ 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_json = make_atom(env, "json"); 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 abe5154..aea0833 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -19,6 +19,7 @@ typedef struct { ERL_NIF_TERM atom_null; ERL_NIF_TERM atom_true; ERL_NIF_TERM atom_false; + ERL_NIF_TERM atom_json; 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 08fa3c0..426b542 100644 --- a/src/jiffy.erl +++ b/src/jiffy.erl @@ -16,7 +16,8 @@ | json_string() | json_number() | json_object() - | json_array(). + | json_array() + | json_preencoded(). -type json_array() :: [json_value()]. -type json_string() :: atom() | binary(). @@ -33,6 +34,8 @@ -endif. +-type json_preencoded() :: {json, Json::binary()}. + -type jiffy_decode_result() :: json_value() | {has_trailer, json_value(), binary()}. diff --git a/test/jiffy_16_preencode_tests.erl b/test/jiffy_16_preencode_tests.erl new file mode 100644 index 0000000..09d73b4 --- /dev/null +++ b/test/jiffy_16_preencode_tests.erl @@ -0,0 +1,53 @@ +-module(jiffy_16_preencode_tests). + + +-include_lib("eunit/include/eunit.hrl"). +-include("jiffy_util.hrl"). + + +preencode_success_test_() -> + [gen(ok, Case) || Case <- cases(ok)]. + + +preencode_failure_test_() -> + [gen(error, Case) || Case <- cases(error)]. + + +gen(ok, {E1, J, E2}) -> + {msg("~p", [E1]), [ + {"Encode", ?_assertEqual(J, enc(E1))}, + {"Decode", ?_assertEqual(E2, dec(J))} + ]}; + +gen(error, E) -> + {msg("Error: ~p", [E]), [ + ?_assertThrow({error, _}, enc(E)) + ]}. + + +cases(ok) -> + TopTests = + lists:map( + fun (EJSON) -> + JSON = enc(EJSON), + {{json, JSON}, JSON, EJSON} + end, [ 123 + , <<"hello world">> + , true + , false + , {[ {<<"a">>, <<"apple">>}, {<<"b">>, <<"banana">>} ]} + ]), + EJSON = [ 1, <<"a">> ], + JSON = enc(EJSON), + BuriedTests = + [ { [ {json, JSON} ], <<"[[1,\"a\"]]">>, [ EJSON ]} + , { [ 1, {json, JSON}, 3 ], <<"[1,[1,\"a\"],3]">>, [ 1, EJSON, 3 ]} + , { [ {json, JSON}, {json, JSON} ], <<"[[1,\"a\"],[1,\"a\"]]">>, [ EJSON, EJSON ]} + , { {[ {<<"a">>, {json, JSON}} ]}, <<"{\"a\":[1,\"a\"]}">>, {[ {<<"a">>, EJSON} ]}} + ], + TopTests ++ BuriedTests; + +cases(error) -> + [ {json, true} + , {json, "true"} + ]. From 9b0b7c0cc096978effb8711211f98c16cac32988 Mon Sep 17 00:00:00 2001 From: David Hull Date: Wed, 15 Mar 2017 16:39:21 +0000 Subject: [PATCH 2/4] Refactor common object encoding to new function enc_object_element. --- c_src/encoder.c | 115 ++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 68 deletions(-) diff --git a/c_src/encoder.c b/c_src/encoder.c index 4d8bb49..a440f77 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -598,6 +598,49 @@ enc_map_to_ejson(ErlNifEnv* env, ERL_NIF_TERM map, ERL_NIF_TERM* out) } #endif +ERL_NIF_TERM +enc_object_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; + + if(first && !enc_start_object(e)) { + return enc_error(e, "internal_error"); + } + if(enif_is_empty_list(env, curr)) { + if(!enc_end_object(e)) { + return enc_error(e, "internal_error"); + } + return 0; + } + if(!enif_get_list_cell(env, curr, &item, &curr)) { + return enc_error(e, "internal_error"); + } + if(!enif_get_tuple(env, item, &arity, &tuple)) { + return enc_obj_error(e, "invalid_object_member", item); + } + if(arity != 2) { + return enc_obj_error(e, "invalid_object_member_arity", item); + } + if(!first && !enc_comma(e)) { + return enc_error(e, "internal_error"); + } + if(!enc_string(e, tuple[0])) { + return enc_obj_error(e, "invalid_object_member_key", tuple[0]); + } + if(!enc_colon(e)) { + return enc_error(e, "internal_error"); + } + 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); + *stackp = stack; + return 0; +} + ERL_NIF_TERM encode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { @@ -712,40 +755,8 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ret = enc_error(e, "internal_error"); goto done; } - if(enif_is_empty_list(env, curr)) { - if(!enc_end_object(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - continue; - } - if(!enif_get_list_cell(env, curr, &item, &curr)) { - ret = enc_error(e, "internal_error"); - goto done; - } - if(!enif_get_tuple(env, item, &arity, &tuple)) { - ret = enc_obj_error(e, "invalid_object_member", item); - goto done; - } - if(arity != 2) { - ret = enc_obj_error(e, "invalid_object_member_arity", item); - goto done; - } - if(!enc_comma(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - if(!enc_string(e, tuple[0])) { - ret = enc_obj_error(e, "invalid_object_member_key", tuple[0]); - goto done; - } - if(!enc_colon(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - 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); + ret = enc_object_element(e, 0, curr, &stack); + if(ret) { goto done; } } else if(enif_is_identical(curr, e->atoms->ref_array)) { if(!enif_get_list_cell(env, stack, &curr, &stack)) { ret = enc_error(e, "internal_error"); @@ -815,40 +826,8 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ret = enc_obj_error(e, "invalid_object", curr); goto done; } - if(!enc_start_object(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - if(enif_is_empty_list(env, tuple[0])) { - if(!enc_end_object(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - continue; - } - if(!enif_get_list_cell(env, tuple[0], &item, &curr)) { - ret = enc_error(e, "internal_error"); - goto done; - } - if(!enif_get_tuple(env, item, &arity, &tuple)) { - ret = enc_obj_error(e, "invalid_object_member", item); - goto done; - } - if(arity != 2) { - ret = enc_obj_error(e, "invalid_object_member_arity", item); - goto done; - } - if(!enc_string(e, tuple[0])) { - ret = enc_obj_error(e, "invalid_object_member_key", tuple[0]); - goto done; - } - if(!enc_colon(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - 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); + ret = enc_object_element(e, 1, tuple[0], &stack); + if (ret) { goto done; } } else if(arity == 2) { if(enif_compare(tuple[0], e->atoms->atom_json) != 0) { ret = enc_obj_error(e, "invalid_ejson", curr); From 5b7cf0bd4efe2ced067a807b23df58d20376ff6f Mon Sep 17 00:00:00 2001 From: David Hull Date: Wed, 15 Mar 2017 17:39:14 +0000 Subject: [PATCH 3/4] Refactor common array encoding to new function enc_array_element. --- c_src/encoder.c | 70 ++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/c_src/encoder.c b/c_src/encoder.c index a440f77..09e0727 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -641,6 +641,36 @@ enc_object_element(Encoder* e, int first, ERL_NIF_TERM curr, ERL_NIF_TERM* stack return 0; } +ERL_NIF_TERM +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; + + if(first && !enc_start_array(e)) { + return enc_error(e, "internal_error"); + } + if(enif_is_empty_list(env, curr)) { + if(!enc_end_array(e)) { + return enc_error(e, "internal_error"); + } + return 0; + } + if(!first && !enc_comma(e)) { + return enc_error(e, "internal_error"); + } + if(!enif_get_list_cell(env, curr, &item, &curr)) { + return enc_error(e, "internal_error"); + } + stack = enif_make_list_cell(env, curr, stack); + stack = enif_make_list_cell(env, e->atoms->ref_array, stack); + stack = enif_make_list_cell(env, item, stack); + *stackp = stack; + return 0; +} + + ERL_NIF_TERM encode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { @@ -762,24 +792,8 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ret = enc_error(e, "internal_error"); goto done; } - if(enif_is_empty_list(env, curr)) { - if(!enc_end_array(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - continue; - } - if(!enc_comma(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - if(!enif_get_list_cell(env, curr, &item, &curr)) { - ret = enc_error(e, "internal_error"); - goto done; - } - stack = enif_make_list_cell(env, curr, stack); - stack = enif_make_list_cell(env, e->atoms->ref_array, stack); - stack = enif_make_list_cell(env, item, stack); + ret = enc_array_element(e, 0, curr, &stack); + if(ret) { goto done; } } else if(enif_compare(curr, e->atoms->atom_null) == 0) { if(!enc_literal(e, "null", 4)) { ret = enc_error(e, "null"); @@ -854,24 +868,8 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 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"); - goto done; - } - if(enif_is_empty_list(env, curr)) { - if(!enc_end_array(e)) { - ret = enc_error(e, "internal_error"); - goto done; - } - continue; - } - if(!enif_get_list_cell(env, curr, &item, &curr)) { - ret = enc_error(e, "internal_error"); - goto done; - } - stack = enif_make_list_cell(env, curr, stack); - stack = enif_make_list_cell(env, e->atoms->ref_array, stack); - stack = enif_make_list_cell(env, item, stack); + ret = enc_array_element(e, 1, curr, &stack); + if(ret) { goto done; } } else { if(!enc_unknown(e, curr)) { ret = enc_error(e, "internal_error"); From 704547f6accb9005827eec653831a303c9dadb3d Mon Sep 17 00:00:00 2001 From: David Hull Date: Wed, 15 Mar 2017 20:18:25 +0000 Subject: [PATCH 4/4] Add jiffy:partial_encode/2 function. --- c_src/encoder.c | 33 +++++++++++++++++++++---------- c_src/jiffy.c | 2 ++ c_src/jiffy.h | 2 ++ src/jiffy.erl | 12 ++++++++++- test/jiffy_16_preencode_tests.erl | 24 +++++++++++++++++++++- 5 files changed, 61 insertions(+), 12 deletions(-) diff --git a/c_src/encoder.c b/c_src/encoder.c index 09e0727..af2585a 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -628,16 +628,28 @@ enc_object_element(Encoder* e, int first, ERL_NIF_TERM curr, ERL_NIF_TERM* stack if(!first && !enc_comma(e)) { return enc_error(e, "internal_error"); } - if(!enc_string(e, tuple[0])) { - return enc_obj_error(e, "invalid_object_member_key", tuple[0]); - } - if(!enc_colon(e)) { - return enc_error(e, "internal_error"); + if(enif_compare(tuple[0], e->atoms->atom_partial_object) == 0) { + if(!enif_is_binary(env, tuple[1])) { + return enc_obj_error(e, "invalid_json_string", curr); + } + if(!enc_json(e, tuple[1])) { + return enc_error(e, "internal_error"); + } + stack = enif_make_list_cell(env, curr, stack); + stack = enif_make_list_cell(env, e->atoms->ref_object, stack); + *stackp = stack; + } else { + if(!enc_string(e, tuple[0])) { + return enc_obj_error(e, "invalid_object_member_key", tuple[0]); + } + if(!enc_colon(e)) { + return enc_error(e, "internal_error"); + } + 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); + *stackp = stack; } - 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); - *stackp = stack; return 0; } @@ -843,7 +855,8 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ret = enc_object_element(e, 1, tuple[0], &stack); if (ret) { goto done; } } else if(arity == 2) { - if(enif_compare(tuple[0], e->atoms->atom_json) != 0) { + if((enif_compare(tuple[0], e->atoms->atom_json) != 0) && + (enif_compare(tuple[0], e->atoms->atom_partial_array) != 0)) { ret = enc_obj_error(e, "invalid_ejson", curr); goto done; } diff --git a/c_src/jiffy.c b/c_src/jiffy.c index 1a8d285..dde4da4 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -17,6 +17,8 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_true = make_atom(env, "true"); st->atom_false = make_atom(env, "false"); st->atom_json = make_atom(env, "json"); + 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 aea0833..a63d77e 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -20,6 +20,8 @@ typedef struct { ERL_NIF_TERM atom_true; ERL_NIF_TERM atom_false; ERL_NIF_TERM atom_json; + 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 426b542..9a0937f 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]). @@ -107,6 +107,16 @@ encode(Data, Options) -> end. +-spec partial_encode(json_array(), encode_options()) -> {'$partial_array$', binary()}; + (json_object(), encode_options()) -> {'$partial_object$', binary()}. +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_16_preencode_tests.erl b/test/jiffy_16_preencode_tests.erl index 09d73b4..062d7de 100644 --- a/test/jiffy_16_preencode_tests.erl +++ b/test/jiffy_16_preencode_tests.erl @@ -19,6 +19,11 @@ gen(ok, {E1, J, E2}) -> {"Decode", ?_assertEqual(E2, dec(J))} ]}; +gen(ok, {E, J}) -> + {msg("~p", [E]), [ + {"Encode", ?_assertEqual(J, enc(E))} + ]}; + gen(error, E) -> {msg("Error: ~p", [E]), [ ?_assertThrow({error, _}, enc(E)) @@ -45,7 +50,24 @@ cases(ok) -> , { [ {json, JSON}, {json, JSON} ], <<"[[1,\"a\"],[1,\"a\"]]">>, [ EJSON, EJSON ]} , { {[ {<<"a">>, {json, JSON}} ]}, <<"{\"a\":[1,\"a\"]}">>, {[ {<<"a">>, EJSON} ]}} ], - TopTests ++ BuriedTests; + + PartialArray = jiffy:partial_encode([ 2, 3 ], []), + PartialArrayTests = + [ {[ PartialArray ], <<"[2,3]">>} + , {[ 1, PartialArray ], <<"[1,2,3]">>} + , {[ PartialArray, 4 ], <<"[2,3,4]">>} + , {[ 1, PartialArray, 4 ], <<"[1,2,3,4]">>} + ], + + PartialObject = jiffy:partial_encode({[ {<<"ii">>, <<"two">>}, {<<"iii">>, 3} ]}, []), + PartialObjectTests = + [ {{[ PartialObject ]}, <<"{\"ii\":\"two\",\"iii\":3}">>} + , {{[ {<<"i">>, 1}, PartialObject ]}, <<"{\"i\":1,\"ii\":\"two\",\"iii\":3}">>} + , {{[ PartialObject, {<<"iv">>, 4} ]}, <<"{\"ii\":\"two\",\"iii\":3,\"iv\":4}">>} + , {{[ {<<"i">>, 1}, PartialObject, {<<"iv">>, 4} ]}, <<"{\"i\":1,\"ii\":\"two\",\"iii\":3,\"iv\":4}">>} + ], + + TopTests ++ BuriedTests ++ PartialArrayTests ++ PartialObjectTests; cases(error) -> [ {json, true}