├── .gitignore ├── src ├── jsonx.app.src ├── jstream.erl └── jsonx.erl ├── test ├── include_json_test.erl ├── array_tests.erl ├── intro_tests.erl ├── num_tests.erl ├── bench_encode_records.erl ├── encode_records_test.erl ├── obj_tests.erl ├── decode_records_test.erl ├── str_tests.erl └── jstream_test.erl ├── Makefile ├── examples ├── example.json ├── records_examples.erl └── stream_example.erl ├── LICENSE ├── rebar.config ├── c_src ├── jsonx.h ├── jsonx.c ├── jsonx_str.h ├── encoder.c ├── jstream.c └── decoder.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .eunit 3 | *.so 4 | *.beam 5 | ebin/*.app 6 | *~ 7 | -------------------------------------------------------------------------------- /src/jsonx.app.src: -------------------------------------------------------------------------------- 1 | %%% -*- mode: erlang -*- 2 | 3 | {application, jsonx, 4 | [ 5 | {description, "JSON library for Erlang writen in C"}, 6 | {vsn, "3.0"}, 7 | {registered, []}, 8 | {applications, [ 9 | kernel, 10 | stdlib 11 | ]}, 12 | {env, []} 13 | ]}. 14 | -------------------------------------------------------------------------------- /test/include_json_test.erl: -------------------------------------------------------------------------------- 1 | -module(include_json_test). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | %% Test encode {json, iolist} 5 | encjs0_test() -> <<>> = jsonx:encode({json, []}). 6 | encjs1_test() -> <<>> = jsonx:encode({json, <<"">>}). 7 | encjs2_test() -> <<"[22,23,24]">> = jsonx:encode({json, <<"[22,23,24]">>}). 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR = rebar 2 | 3 | .PHONY: all xref erl test clean doc 4 | 5 | all: clean erl xref test 6 | 7 | erl: 8 | $(REBAR) compile 9 | 10 | xref: 11 | $(REBAR) xref 12 | 13 | test: 14 | $(REBAR) eunit 15 | 16 | clean: 17 | @$(REBAR) clean 18 | @-rm -rvf deps ebin doc .eunit 19 | @-rm README.html 20 | @-rm c_src/*.o 21 | @-rm priv/*.so 22 | @-rm */*~ 23 | 24 | doc: 25 | $(REBAR) doc 26 | -------------------------------------------------------------------------------- /test/array_tests.erl: -------------------------------------------------------------------------------- 1 | -module(array_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | %% Test encode list 5 | encl0_test() -> <<"[]">> = jsonx:encode([]). 6 | encl1_test() -> <<"[[[]]]">> = jsonx:encode([[[]]]). 7 | encl2_test() -> <<"[true,1,1.1,\"bin\"]">> = jsonx:encode([true, 1, 1.1, <<"bin">>]). 8 | 9 | %% Test decode array 10 | decarr0_test() -> 11 | [] = jsonx:decode(<<"[]">>). 12 | decarr1_test() -> 13 | [[],[[]],[1],[true,false]] = jsonx:decode(<<" [[], [[]], [1], [true, false]] ">>). 14 | -------------------------------------------------------------------------------- /examples/example.json: -------------------------------------------------------------------------------- 1 | {"widget": { 2 | "debug": "on", 3 | "window": { 4 | "title": "Sample Konfabulator Widget", 5 | "name": "main_window", 6 | "width": 500, 7 | "height": 500 8 | }, 9 | "image": { 10 | "src": "Images/Sun.png", 11 | "name": "sun1", 12 | "hOffset": 250, 13 | "vOffset": 250, 14 | "alignment": "center" 15 | }, 16 | "text": { 17 | "data": "Click Here", 18 | "size": 36, 19 | "style": "bold", 20 | "name": "text1", 21 | "hOffset": 250, 22 | "vOffset": 100, 23 | "alignment": "center", 24 | "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" 25 | } 26 | }} 27 | -------------------------------------------------------------------------------- /test/intro_tests.erl: -------------------------------------------------------------------------------- 1 | -module(intro_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | 5 | %% Test trailing data 6 | trail_test() -> 7 | {error,trailing_data,4} = jsonx:decode(<<"123 ,">>). 8 | 9 | %% Test stack 10 | stack0_test() -> 11 | L = lists:seq(0,99), 12 | LL = L ++ L, 13 | LL = jsonx:decode(jsonx:encode(LL)). 14 | 15 | stack1_test() -> 16 | L = {[{<<"a">>, lists:seq(0,99)}|| _X <- lists:seq(0,99)]}, 17 | L = jsonx:decode(jsonx:encode(L)). 18 | 19 | stack2_test() -> 20 | D = jsonx:decoder([ {r0, [q,w,e,r,t,y,u,i,o,p, a,s,d,f,g,h,j,k,l,z ]} ]), 21 | E = jsonx:encoder([ {r0, [q,w,e,r,t,y,u,i,o,p, a,s,d,f,g,h,j,k,l,z ]} ]), 22 | JTerm = list_to_tuple([r0|lists:seq(1,20)]), 23 | Json = E(JTerm), 24 | JTerm = D(Json ). 25 | 26 | -------------------------------------------------------------------------------- /examples/records_examples.erl: -------------------------------------------------------------------------------- 1 | -module(records_examples). 2 | -compile(export_all). 3 | 4 | -record(person, {name, age, friends}). 5 | -record(person2, {name, age, phone}). 6 | 7 | encoder1() -> 8 | jsonx:encoder([{person, record_info(fields, person)}, 9 | {person2, record_info(fields, person2)} ]). 10 | 11 | encoder1_ignore() -> 12 | jsonx:encoder([{person, record_info(fields, person)}, 13 | {person2, record_info(fields, person2)} ], [{ignore, [null]}]). 14 | 15 | decoder1() -> 16 | jsonx:decoder([{person, record_info(fields, person)}, 17 | {person2, record_info(fields, person2)}]). 18 | 19 | nonstrict_decoder1() -> 20 | jsonx:decoder([{person, record_info(fields, person)}, 21 | {person2, record_info(fields, person2)}], [{format, proplist}]). 22 | -------------------------------------------------------------------------------- /test/num_tests.erl: -------------------------------------------------------------------------------- 1 | -module(num_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | %% Test encode numeric 5 | enc1_test() -> <<"1">> = jsonx:encode(1). 6 | enc2_test() -> <<"-1">> = jsonx:encode(-1). 7 | enc3_test() -> <<"123456789012345">> = jsonx:encode(123456789012345). 8 | enc4_test() -> <<"-123456789012345">> = jsonx:encode(-123456789012345). 9 | enc5_test() -> <<"-1">> = jsonx:encode(-1.0). 10 | enc6_test() -> <<"-1.1">> = jsonx:encode(-1.1). 11 | enc7_test() -> <<"11.11">> = jsonx:encode(11.11). 12 | enc8_test() -> <<"11">> = jsonx:encode(11.00). 13 | enc9_test() -> <<"65">> = jsonx:encode($A). 14 | 15 | %% Test decode numerics 16 | decnum0_test() -> 17 | 0 = jsonx:decode(<<"0">>). 18 | decnumb_test() -> 19 | 1234567890123456789 = jsonx:decode(<<"1234567890123456789">>). 20 | decnumm_test() -> 21 | -123 = jsonx:decode(<<"-123">>). 22 | decnum00_test() -> 23 | 0.0 = jsonx:decode(<<"0.0">>). 24 | decnum1_test() -> 25 | -0.0012 = jsonx:decode(<<"-1.2e-3">>). 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is the MIT license. 2 | 3 | Copyright (c) 2013 Yuriy Iskra . 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %%% -*- mode: erlang -*- 2 | 3 | %% Erlang compiler options 4 | {erl_opts, [debug_info, 5 | warnings_as_errors, 6 | warn_export_all 7 | %% warn_untyped_record 8 | ]}. 9 | 10 | {erl_opts, [ 11 | debug_info, 12 | warnings_as_errors, 13 | warn_export_all 14 | ]}. 15 | 16 | {xref_checks, [ 17 | undefined_function_calls 18 | ]}. 19 | {validate_app_modules, true}. 20 | 21 | {port_specs, [ 22 | {"priv/jsonx.so", ["c_src/jsonx.c", "c_src/decoder.c", "c_src/encoder.c"]} 23 | ,{"priv/jstream.so",["c_src/jstream.c"]} 24 | ]}. 25 | 26 | {port_env, [ 27 | {".*", "CXXFLAGS", "$CXXFLAGS -g -Wall -Werror -O3"}, 28 | 29 | %% OS X Leopard flags for 64-bit 30 | {"darwin9.*-64$", "CXXFLAGS", "-m64"}, 31 | {"darwin9.*-64$", "LDFLAGS", "-arch x86_64"}, 32 | 33 | %% OS X Snow Leopard flags for 32-bit 34 | {"darwin10.*-32$", "CXXFLAGS", "-m32"}, 35 | {"darwin10.*-32$", "LDFLAGS", "-arch i386"}, 36 | 37 | %% This will merge into basho/rebar/rebar.config eventually 38 | {"win32", "CFLAGS", "/Wall /DWIN32 /D_WINDOWS /D_WIN32 /DWINDOWS"}, 39 | {"win32", "CXXFLAGS", "-g -Wall -O3"} 40 | ]}. 41 | -------------------------------------------------------------------------------- /examples/stream_example.erl: -------------------------------------------------------------------------------- 1 | -module(stream_example). 2 | -compile(export_all). 3 | 4 | run() -> 5 | run("example.json"). 6 | 7 | run(FileName) -> 8 | {ok,FD} = file:open(FileName, read), 9 | Dec = jstream:new_decoder(<<>>), 10 | R = all_events_from_lines(FD, Dec, {[], <<"">>}), 11 | file:close(FD), R. 12 | 13 | 14 | all_events_from_lines(FD, Dec, {Path, CurKey} = State) -> 15 | D1 = jstream:get_event(Dec), 16 | %%io:format("~w~n", [D1]), 17 | case D1 of 18 | parse_buf -> 19 | %%lists:reverse([D1 | Acc]); 20 | {ok, Ln} = file:read_line(FD), 21 | ok = jstream:update_decoder(Dec, list_to_binary(Ln)), 22 | all_events_from_lines(FD, Dec, State); 23 | {error, _E } -> 24 | D1; 25 | {parse_end, _B} -> 26 | ok; 27 | start_map -> 28 | all_events_from_lines(FD, Dec, {[CurKey|Path], CurKey}); 29 | end_map -> 30 | [_ | P] = Path, 31 | all_events_from_lines(FD, Dec, {P, CurKey}); 32 | {map_key, K} -> 33 | all_events_from_lines(FD, Dec, {Path, K}); 34 | X -> 35 | io:format("~s = ~p~n", [ join_r([CurKey|Path], <<"/">>), X ]), 36 | all_events_from_lines(FD, Dec, State) 37 | end. 38 | 39 | join_r(List, Sep) -> 40 | join_r(List, Sep, []). 41 | 42 | join_r([], _Sep, Acc) -> 43 | Acc; 44 | join_r([T], _Sep, Acc) -> 45 | [T|Acc]; 46 | join_r([H|Ts], Sep, Acc) -> 47 | join_r(Ts, Sep, [Sep, H] ++ Acc). 48 | -------------------------------------------------------------------------------- /test/bench_encode_records.erl: -------------------------------------------------------------------------------- 1 | -module(bench_encode_records). 2 | -export([test/0]). 3 | -record(person, {name, last_name, age, adress, phones, email}). 4 | 5 | data() -> 6 | #person{name = <<"Baba">>, 7 | last_name = <<"Yaga">>, 8 | age = 116, 9 | adress = <<"ИзбаНаОкорочках, Болото, Лес, Россия">>, 10 | phones = [<<"666-66-66">>], 11 | email = 'baba_yaga@les.ru' 12 | }. 13 | 14 | test() -> 15 | Format = "~7s~12s~12s~12s~12s~12s~n", 16 | io:format(Format, ["Record", "Jsonx", "Jsonx", "Jiffy", "Jiffy", "jsonx"]), 17 | io:format(Format, ["Count", "Out Size", "Time", "Out Size", "Time", "vs jiffy"]), 18 | [test(X) || X <- [1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683]]. 19 | 20 | test(Times) -> 21 | FJsonx = jsonx:encoder([ {person, record_info(fields, person)} ]), 22 | 23 | Names = record_info(fields, person), 24 | FJiffy = fun(Rs) -> 25 | jiffy:encode( [{lists:zip(Names, tuple_tail(R))} || R <- Rs] ) 26 | end, 27 | Data = [data() || _X <- lists:seq(1, Times)], 28 | _A = timer:tc(FJiffy, [Data]), 29 | _B = timer:tc(FJsonx, [Data]), 30 | {Jf_tc, Jf_bin} = timer:tc(FJiffy, [Data]), 31 | {Jx_tc, Jx_bin} = timer:tc(FJsonx, [Data]), 32 | Out = [Times, size(Jx_bin), Jx_tc, size(Jf_bin), Jf_tc, Jf_tc/Jx_tc], 33 | io:format("~7b~11bb~12b~11bb~12b~12.2f~n", Out). 34 | 35 | %% . 36 | tuple_tail(Rec) -> 37 | [_H|RT] = tuple_to_list(Rec), 38 | RT. 39 | -------------------------------------------------------------------------------- /test/encode_records_test.erl: -------------------------------------------------------------------------------- 1 | -module(encode_records_test). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | %% Tests encode records 5 | encrec0_test() -> 6 | F = jsonx:encoder([ {none, []}, {person, [name, age]}, {person2, [name, age, phone]} ]), 7 | <<"[{},{\"name\": \"IvanDurak\",\"age\": 16},{\"name\": \"BabaYaga\",\"age\": 116,\"phone\": 6666666}]">> 8 | = F([ {none}, {person,<<"IvanDurak">>,16}, {person2, <<"BabaYaga">>, 116, 6666666} ]). 9 | 10 | -record(none, {}). 11 | -record(person, {name :: binary(), age :: number()}). 12 | -record(person2, {name, age, phone}). 13 | -record(rec, {a, b}). 14 | 15 | encrec1_test() -> 16 | F = jsonx:encoder([ {none, record_info(fields, none)}, {person, record_info(fields, person)}, {person2, record_info(fields, person2)} ]), 17 | <<"[{},{\"name\": \"IvanDurak\",\"age\": 16},{\"name\": \"BabaYaga\",\"age\": 116,\"phone\": 6666666}]">> 18 | = F([ {none}, {person,<<"IvanDurak">>,16}, {person2, <<"BabaYaga">>, 116, 6666666} ]). 19 | 20 | encrec2_test() -> 21 | F = jsonx:encoder([ {rec, record_info(fields, rec)} ]), 22 | <<"{\"a\": \"x\",\"b\": {\"a\": {\"a\": \"x\",\"b\": \"y\"},\"b\": \"w\"}}">> = F({rec, x, {rec, {rec, x, y}, w}}). 23 | 24 | 25 | 26 | encrec3_test() -> 27 | %%F = jsonx:encoder([ {spam, [a, b, c]} ], [{ignore,null}]), 28 | F = jsonx:encoder([ {spam, [a, b, c]} ], [{ignore, [null, undefined]}]), 29 | <<"{}">> = F({spam, null, null, null}), 30 | <<"{\"a\": \"nall\"}">> = F({spam, nall, null, undefined}). 31 | -------------------------------------------------------------------------------- /test/obj_tests.erl: -------------------------------------------------------------------------------- 1 | -module(obj_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | %% %% Test encode empty object 5 | enco0_test() -> {no_match, {}} = jsonx:encode({}). 6 | 7 | %% Test encode proplist 8 | encpl0_test() -> <<"{\"a\":1}">> = jsonx:encode([{a,1}]). 9 | encpl1_test() -> <<"{\"a\":{\"b\":2}}">> = jsonx:encode([{a, [{b, 2}]}]). 10 | 11 | %% Test encode eep18 12 | enceep0_test() -> <<"{}">> = jsonx:encode({[]}). 13 | enceep1_test() -> <<"{\"a\":1}">> = jsonx:encode({[{a,1}]}). 14 | enceep2_test() -> <<"{\"a\":1,\"b\":2}">>= jsonx:encode({[{a,1},{b,2}]}). 15 | 16 | %% Test encode struct 17 | encst0_test() -> <<"{}">> = jsonx:encode({struct, []}). 18 | encst1_test() -> <<"{\"a\":1}">> = jsonx:encode({struct, [{a,1}]}). 19 | encst2_test() -> <<"{\"a\":1,\"b\":2}">>= jsonx:encode({struct, [{a,1},{b,2}]}). 20 | 21 | %% Test decode object 22 | decobj0_test() -> 23 | {[]} = jsonx:decode(<<"{}">>). 24 | decobj1_test() -> 25 | {[{<<"a">>,1}]} = jsonx:decode(<<"{\"a\": 1}">>). 26 | decobj2_test() -> 27 | {[{<<"a">>,1}, {<<"XX">>, [true, false]}]} = jsonx:decode(<<"{\"a\": 1, \"XX\": [true, false]}">>). 28 | 29 | %% Test decode/2 30 | dec2obj0_test() -> 31 | {[]} = jsonx:decode(<<"{}">>, []). 32 | dec2obj1_test() -> 33 | {struct,[]} = jsonx:decode(<<"{}">>, [{format, struct}]). 34 | dec2obj2_test() -> 35 | {[]} = jsonx:decode(<<"{}">>, [{format, eep18}]). 36 | dec2obj3_test() -> 37 | [] = jsonx:decode(<<"{}">>, [{format, proplist}]). 38 | dec2obj4_test() -> 39 | {[]} = jsonx:decode(<<"{}">>, [{format, eep18}, {format, struct}, {format, proplist}]). 40 | -------------------------------------------------------------------------------- /test/decode_records_test.erl: -------------------------------------------------------------------------------- 1 | -module(decode_records_test). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | -record(r0, {}). 5 | -record(r1, {a}). 6 | -record(r2, {a,b}). 7 | -record(r4, {c,b,q,p}). 8 | 9 | decrec0_test() -> 10 | D = jsonx:decoder([{r0,record_info(fields, r0)}, {r1,record_info(fields, r1)}, {r2, record_info(fields,r2)}, {r4,record_info(fields, r4)}]), 11 | E = jsonx:encoder([{r0,record_info(fields, r0)}, {r1,record_info(fields, r1)}, {r2, record_info(fields,r2)}, {r4,record_info(fields, r4)}]), 12 | JTerm = [{r0}, {r1, 11}, {r2, 21, 22}, {r4, 42,41,44,43}, {r2, 211, {r2, 311, 312}}], 13 | Json = E(JTerm), 14 | JTerm = D(Json). 15 | 16 | decrec1_test() -> 17 | D = jsonx:decoder([{r0,record_info(fields, r0)}, {r1,record_info(fields, r1)}, {r2, record_info(fields,r2)}, {r4,record_info(fields, r4)}]), 18 | E = jsonx:encoder([{r0,record_info(fields, r0)}, {r1,record_info(fields, r1)}, {r2, record_info(fields,r2)}, {r4,record_info(fields, r4)}]), 19 | JTerm = {r4, {r1, [{r1, {r0}}]}, 413, {r2, 21, {r2, 211, 212}}, 414}, 20 | Json = E(JTerm), 21 | JTerm = D(Json). 22 | 23 | %% Test nonstrict decoder 24 | decrec2_test() -> 25 | ND = jsonx:decoder([{r0,record_info(fields, r0)}, {r1,record_info(fields, r1)}, {r2, record_info(fields,r2)}, {r4,record_info(fields, r4)}], [{format, eep18}]), 26 | E = jsonx:encoder([{r0,record_info(fields, r0)}, {r1,record_info(fields, r1)}, {r2, record_info(fields,r2)}, {r4,record_info(fields, r4)}]), 27 | JTerm = [{r4, 1, 2, 3, 4}, [{record, undefined}, {strict, false}]], 28 | Json = E(JTerm), 29 | Json = E(ND(Json)). 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/jstream.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013 Yuriy Iskra 2 | 3 | %% @doc Write Me 4 | %% 5 | 6 | -module(jstream). 7 | -export([new_decoder/1, update_decoder/2, get_event/1]). 8 | -on_load(init/0). 9 | -define(LIBNAME, jstream). 10 | -define(APPNAME, jsonx). 11 | 12 | %% ================== 13 | %% API Decoding doc 14 | %% ================== 15 | 16 | %%@doc Make new decocoder. 17 | -spec new_decoder(JSON) -> DECODER when 18 | JSON :: binary(), 19 | DECODER :: any(). 20 | new_decoder(_JSON) -> 21 | not_loaded(?LINE). 22 | 23 | %%@doc Add new data to decoder. 24 | -spec update_decoder(DECODER, JSON) -> ok when 25 | JSON :: binary(), 26 | DECODER :: any(). 27 | update_decoder(_DECODER, _JSON) -> 28 | not_loaded(?LINE). 29 | 30 | %%@doc Get next event. 31 | -spec get_event(DECODER) -> EVENT when 32 | DECODER :: any(), 33 | EVENT :: [true | false | null | binary() | number() | 34 | start_map| {map_key, binary()} | end_map | 35 | start_array | end_array | 36 | {error, any()} | parse_buf | parse_end]. 37 | get_event(_DECODER) -> 38 | not_loaded(?LINE). 39 | 40 | %% Init 41 | 42 | init() -> 43 | So = case code:priv_dir(?APPNAME) of 44 | {error, bad_name} -> 45 | case filelib:is_dir(filename:join(["..", priv])) of 46 | true -> 47 | filename:join(["..", priv, ?LIBNAME]); 48 | _ -> 49 | filename:join([priv, ?LIBNAME]) 50 | end; 51 | Dir -> 52 | filename:join(Dir, ?LIBNAME) 53 | end, 54 | erlang:load_nif(So, [null, start_map, map_key, end_map, start_array, end_array, parse_buf, parse_end, 55 | big_num, invalid_string, invalid_json]). 56 | 57 | not_loaded(Line) -> 58 | exit({not_loaded, [{module, ?MODULE}, {line, Line}]}). 59 | 60 | 61 | -------------------------------------------------------------------------------- /test/str_tests.erl: -------------------------------------------------------------------------------- 1 | -module(str_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | %% Test encode atom 5 | enca0_test() -> <<"\"\"">> = jsonx:encode(''). 6 | enca1_test() -> <<"\"atom\"">> = jsonx:encode(atom). 7 | enca2_test() -> <<"[true,null,false,\"atom\"]">> = jsonx:encode([true, null, false, atom]). 8 | enca3_test() -> <<"{\"null\":null,\"atom\":\"atom\"}">> = jsonx:encode([{null,null},{atom,atom}]). 9 | 10 | %% Test encode binary 11 | encb0_test() -> <<"\"\"">> = jsonx:encode(''). 12 | encb1_test() -> <<"\"binary\"">> = jsonx:encode(binary). 13 | encb2_test() -> <<"\"binary\"">> = jsonx:encode('binary'). 14 | 15 | %% Test encode with escape string 16 | encesc0_test() -> 17 | <<"\"\\b\\t\\n\\v\\f\\r\\\"'/\\\\\"">> = jsonx:encode(<<8,9,10,11,12,13,34,39,47,92>>). 18 | encesc1_test() -> 19 | <<"\"\\u0000\\u0007\\u000e\\u001f\\u007f\"">> = jsonx:encode(<<0,7,14,31,127>>). 20 | encesc2_test() -> 21 | <<"\"\\u0000\\b\\r\\u000e\\u001f\\u007f\"">> = jsonx:encode('\000\b\r\016\037\d'). 22 | %% Test encode with validate utf-8 string 23 | encutf0_test() -> 24 | <<34,192,128,224,128,128,240,128,128,128,34>> = jsonx:encode(<<192, 128, 224, 128, 128, 240, 128, 128, 128>>). 25 | encutf1_test() -> 26 | {no_match,<<128>>} = jsonx:encode(<<128>>). 27 | encutf2_test() -> 28 | {no_match,<<191, 128>>} = jsonx:encode(<<191, 128>>). 29 | encutf3_test() -> 30 | {no_match,<<224, 128>>} = jsonx:encode(<<224, 128>>). 31 | encutf4_test() -> 32 | {no_match,<<240, 128, 128>>} = jsonx:encode(<<240, 128, 128>>). 33 | encutf5_test() -> 34 | {no_match,<<248,128,128,128,128>>} = jsonx:encode(<<248, 128, 128, 128, 128>>). 35 | 36 | 37 | %% Test decode atoms 38 | dectrue_test() -> 39 | true = jsonx:decode(<<"true">>). 40 | decfalse_test() -> 41 | false = jsonx:decode(<<"false">>). 42 | decnull_test() -> 43 | null = jsonx:decode(<<"null">>). 44 | 45 | %% Test decode string 46 | decstr0_test() -> 47 | <<>> = jsonx:decode(<<"\"\"">>). 48 | decstr1_test() -> 49 | <<"...">> = jsonx:decode(<<"\"...\"">>). 50 | decstr2_test() -> 51 | <<192, 128, 224, 128, 128, 240, 128, 128, 128>> = jsonx:decode(<<34,192,128,224,128,128,240,128,128,128,34>>). 52 | decstr3_test() -> 53 | <<"/">> = jsonx:decode(<<"\"\\/\"">>). 54 | decstre0_test() -> 55 | {error,invalid_string,0} = jsonx:decode(<<34,192,34>>). 56 | decstre01_test() -> 57 | {error,invalid_string,0} = jsonx:decode(<<34,0,34>>). 58 | decstre02_test() -> 59 | {error,invalid_string,0} = jsonx:decode(<<34,127,34>>). 60 | decstre1_test() -> 61 | {error,invalid_string,0} = jsonx:decode(<<34,224,128,34>>). 62 | decstre2_test() -> 63 | {error,invalid_string,0} = jsonx:decode(<<34,240,128,128,34>>). 64 | decstre3_test() -> 65 | {error,invalid_string,0} = jsonx:decode(<<34,255,34>>). 66 | decstresc1_test() -> 67 | [<<".\b.\t.\v.\f.\r.\".\\.">>] = jsonx:decode(<<"[\".\\b.\\t.\\v.\\f.\\r.\\\".\\\\.\"]">>). 68 | decstresc2_test() -> 69 | <<9,192,191,224,191,191,240,191,191,128,10>> =jsonx:decode(<<34,"\\t",192,191,224,191,191,240,191,191,128,"\\n",34 >>). 70 | decstrescerr1_test() -> 71 | {error,invalid_string,0} = jsonx:decode(<<34,"\\t",192,34 >>). 72 | decstrescerr2_test() -> 73 | {error,invalid_string,0} = jsonx:decode(<<34,"\\n",224,128,34 >>). 74 | decstrescerr3_test() -> 75 | {error,invalid_string,0} = jsonx:decode(<<34,"\\r",240,128,128,34 >>). 76 | decstrescu1_test() -> 77 | <<"Dn..">> = jsonx:decode(<<34,"\\u0044\\u006e..",34>>). 78 | decstrescu2_test() -> 79 | <<194,128>> = jsonx:decode(<<34,"\\u0080",34>>). 80 | -------------------------------------------------------------------------------- /c_src/jsonx.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Yuriy Iskra 2 | 3 | #include "erl_nif.h" 4 | 5 | #ifdef _MSC_VER 6 | #define inline __inline 7 | #endif 8 | 9 | typedef struct{ 10 | ERL_NIF_TERM am_true; 11 | ERL_NIF_TERM am_false; 12 | ERL_NIF_TERM am_null; 13 | 14 | ERL_NIF_TERM am_error; 15 | ERL_NIF_TERM am_erange; 16 | ERL_NIF_TERM am_estr; 17 | ERL_NIF_TERM am_esyntax; 18 | ERL_NIF_TERM am_etrailing; 19 | ERL_NIF_TERM am_undefined_record; 20 | 21 | ERL_NIF_TERM am_json; 22 | ERL_NIF_TERM am_struct; 23 | ERL_NIF_TERM am_proplist; 24 | ERL_NIF_TERM am_eep18; 25 | ERL_NIF_TERM am_no_match; 26 | 27 | ErlNifResourceType* encoder_RSTYPE; 28 | ErlNifResourceType* decoder_RSTYPE; 29 | }PrivData; 30 | 31 | ERL_NIF_TERM decode_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 32 | ERL_NIF_TERM encode_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 33 | 34 | /* 35 | Records descriptor for encode 36 | 37 | +---------------------------------------+ <- 0 38 | | sizeof(EncEntry) | 39 | | ----> [binary storage] 40 | | .... | 41 | +---------------------------------------+ <- (EncRecord*) ((void*)entry + sizeof(EncEntry)) 42 | | sizeof(EncRecord) * Entry.records_cnt | 43 | | ... | 44 | +---------------------------------------+ <- (EncField*) ((void*)entry + sizeof(EncEntry) + (sizeof(EncRecord) * entry->records_cnt)) 45 | | sizeof(EncField) * Entry.fields_cnt | 46 | | ... | 47 | +---------------------------------------+ 48 | 49 | // %% %% Records descriptions for encode 50 | // %% { 51 | // %% Rcnt %% argv[0], Records count 52 | // %% ,Fcnt %% argv[1], Counter all fields in records 53 | // %% ,Records = [{Tag, Fields_off, Arity}] %% argv[2], List of records tag, position and length fields 54 | // %% ,Fields = [{Name_off, Size}] %% argv[3], List of position and size fields names in binary storage 55 | // %% ,Bsz %% argv[4], Binary data size 56 | // %% ,Bin %% argv[5], Binary storage for names of fields, format - <,"name": > 57 | // %% } 58 | */ 59 | 60 | typedef struct{ 61 | unsigned offset; // set off in binary storage 62 | unsigned size; 63 | }EncField; 64 | 65 | typedef struct{ 66 | ERL_NIF_TERM tag; //atom 67 | unsigned arity; 68 | unsigned fds_offset; 69 | }EncRecord; 70 | 71 | typedef struct{ 72 | ErlNifBinary bin; 73 | unsigned records_cnt; 74 | unsigned fields_cnt; 75 | ERL_NIF_TERM* ignored; 76 | unsigned ignored_len; 77 | }EncEntry; 78 | 79 | static inline EncRecord* 80 | enc_records_base(EncEntry *entry){ 81 | return (EncRecord*)((void*)entry + sizeof(EncEntry)); 82 | } 83 | 84 | static inline EncField* 85 | enc_fields_base(EncEntry *entry){ 86 | return (EncField*)((void*)entry + sizeof(EncEntry) + (sizeof(EncRecord) * entry->records_cnt)); 87 | } 88 | 89 | static inline size_t 90 | enc_resource_size(unsigned rec_cnt, unsigned field_cnt){ 91 | return sizeof(EncEntry) + sizeof(EncRecord)*rec_cnt + sizeof(EncField)*field_cnt; 92 | } 93 | 94 | /** Records descriptor for decoder 95 | %% { RecCnt %% Records Counter 96 | %% , UKeyCnt %% Uniq Keys Counter 97 | %% , KeyCnt %% Keys Counter 98 | %% , UKeys %% [Key] 99 | %% , Keys %% [KeyNum] 100 | %% , Records3 %% [{Tag, Off, Len}] 101 | %% }; 102 | %% 103 | */ 104 | 105 | #define BITS_PER_WORD (sizeof(long) * 8) 106 | #define BITS_TO_WORDS(nb) ((nb + BITS_PER_WORD - 1) / BITS_PER_WORD) 107 | #define BITS_TO_BYTES(nb) (BITS_TO_WORDS(nb) * sizeof(long)) 108 | #define BITS_TO_ETERM(nb) (BITS_TO_WORDS(nb) * sizeof(long) / sizeof(ERL_NIF_TERM)) 109 | 110 | static inline void 111 | set_bit(int nb, long *ptr){ 112 | ptr[nb / BITS_PER_WORD] |= 1UL << (nb % BITS_PER_WORD); 113 | } 114 | 115 | static inline int 116 | cmp_mask(int nwords, long *ptr1, long *ptr2){ 117 | int i; 118 | for(i = 0; i < nwords; i++){ 119 | if(ptr1[i] != ptr2[i]) 120 | return 0; 121 | } 122 | return 1; 123 | } 124 | //return -1 on not search 125 | static inline int 126 | find_mask(unsigned nb, long *pattern, unsigned nelem, long *masks){ 127 | unsigned ws = BITS_TO_WORDS(nb); 128 | int i; 129 | for(i = 0; i < nelem; i++){ 130 | long *ptr = masks + i * ws; 131 | if(cmp_mask(ws, pattern, ptr)) 132 | return i; 133 | } 134 | return -1; 135 | } 136 | 137 | typedef struct{ 138 | ERL_NIF_TERM tag; //atom 139 | unsigned keys_off; 140 | unsigned arity; 141 | }DecRecord; 142 | 143 | typedef struct{ 144 | unsigned records_cnt; 145 | unsigned ukeys_cnt; 146 | unsigned keys_cnt; 147 | }DecEntry; 148 | 149 | static inline ERL_NIF_TERM* 150 | ukeys_base(DecEntry *dec_entry){ 151 | return (ERL_NIF_TERM *)((void *)dec_entry + sizeof(DecEntry)) ; 152 | } 153 | 154 | static inline size_t 155 | ukeys_size(unsigned ukeys_cnt){ 156 | return ukeys_cnt * sizeof(ERL_NIF_TERM); 157 | } 158 | 159 | static inline unsigned* 160 | keys_base(DecEntry *dec_entry, unsigned ukeys_cnt){ 161 | return (unsigned *)((void *)ukeys_base(dec_entry) + ukeys_size(ukeys_cnt)); 162 | } 163 | 164 | static inline size_t 165 | keys_size(unsigned keys_cnt){ 166 | return keys_cnt * sizeof(unsigned); 167 | } 168 | 169 | static inline DecRecord* 170 | records_base(DecEntry *dec_entry, unsigned ukeys_cnt, unsigned keys_cnt){ 171 | return (DecRecord*)((void *)keys_base(dec_entry, ukeys_cnt) + keys_size(keys_cnt)); 172 | } 173 | 174 | static inline size_t 175 | records_size(unsigned records_cnt){ 176 | return records_cnt * sizeof(DecRecord); 177 | } 178 | 179 | static inline long * 180 | bit_mask_base(DecEntry *dec_entry, unsigned ukeys_cnt, unsigned keys_cnt, unsigned records_cnt){ 181 | return (long *)((void *)records_base(dec_entry, ukeys_cnt, keys_cnt) + records_size(records_cnt)); 182 | } 183 | 184 | static inline size_t 185 | bit_mask_array_size(unsigned ukeys_cnt, unsigned records_cnt){ 186 | return records_cnt * BITS_TO_BYTES(ukeys_cnt); 187 | } 188 | 189 | static inline size_t 190 | dec_resource_size(unsigned records_cnt, unsigned ukeys_cnt, unsigned keys_cnt){ 191 | size_t ret = sizeof(DecEntry) + ukeys_size(ukeys_cnt) + keys_size(keys_cnt) 192 | + records_size(records_cnt) + bit_mask_array_size(ukeys_cnt, records_cnt); 193 | return ret; 194 | } 195 | -------------------------------------------------------------------------------- /test/jstream_test.erl: -------------------------------------------------------------------------------- 1 | -module(jstream_test). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | -export([all_events/1, all_events_from_blist/1]). 4 | 5 | all_events(J) when is_binary(J) -> 6 | D = jstream:new_decoder(J), 7 | all_events(D, []). 8 | 9 | all_events(D, Acc) -> 10 | case D1 = jstream:get_event(D) of 11 | parse_buf -> 12 | lists:reverse([D1 | Acc]); 13 | {error, _ } -> 14 | lists:reverse([D1 | Acc]); 15 | {parse_end, _B} -> 16 | lists:reverse([D1 | Acc]); 17 | X -> 18 | all_events(D, [X|Acc]) 19 | end. 20 | 21 | all_events_from_blist([J|Js]) -> 22 | D = jstream:new_decoder(J), 23 | all_events_from_blist(D, Js, []). 24 | 25 | all_events_from_blist(D, Js, Acc) -> 26 | case D1 = jstream:get_event(D) of 27 | parse_buf -> 28 | %%lists:reverse([D1 | Acc]); 29 | [J| JTs] = Js, 30 | ok = jstream:update_decoder(D, J), 31 | all_events_from_blist(D, JTs, [D1|Acc]); 32 | {error, _ } -> 33 | lists:reverse([D1 | Acc]); 34 | {parse_end, _B} -> 35 | lists:reverse([D1 | Acc]); 36 | _ -> 37 | all_events_from_blist(D, Js, [D1|Acc]) 38 | end. 39 | 40 | scalar_test() -> 41 | [1,{parse_end,<<>>}] = jstream_test:all_events(<<" 1 ">>), 42 | [1.2,{parse_end,<<>>}] = jstream_test:all_events(<<" 1.2 ">>), 43 | [true,{parse_end,<<>>}] = jstream_test:all_events(<<" true ">>), 44 | [false,{parse_end,<<>>}] = jstream_test:all_events(<<" false ">>), 45 | [null,{parse_end,<<>>}] = jstream_test:all_events(<<" null ">>), 46 | [<<"String">>,{parse_end,<<>>}] = jstream_test:all_events(<<" \"String\" ">>), 47 | [<<"String">>,{parse_end,<<"rest">>}] = jstream_test:all_events(<<" \"String\" rest">>). 48 | 49 | array_test() -> 50 | [start_array,end_array,{parse_end,<<>>}] = jstream_test:all_events(<<" [] ">>), 51 | [{error,invalid_json}] = jstream_test:all_events(<<"] ">>), 52 | [start_array,parse_buf] = jstream_test:all_events(<<"[ ">>), 53 | [start_array,{error,invalid_json}] = jstream_test:all_events(<<"[, ">>), 54 | [start_array,start_array,end_array,{error,invalid_json}] = jstream_test:all_events(<<"[[],]">>), 55 | [start_array,1,end_array,{parse_end,<<>>}] = jstream_test:all_events(<<"[1] ">>), 56 | [start_array,1,2,end_array,{parse_end,<<>>}] = jstream_test:all_events(<<"[1,2] ">>), 57 | [start_array,1,2,{error,invalid_json}] = jstream_test:all_events(<<"[1,2x] ">>), 58 | [start_array,1,2,{error,invalid_json}] = jstream_test:all_events(<<"[1,2,] ">>), 59 | [start_array,start_array,end_array,end_array,{parse_end,<<>>}] = jstream_test:all_events(<<"[[]]">>), 60 | [start_array,start_array,1,end_array,end_array, {parse_end,<<>>}] = jstream_test:all_events(<<"[[1]] ">>), 61 | [start_array,start_array,1,end_array,end_array, {parse_end,<<"rest">>}] = jstream_test:all_events(<<"[[1]] rest">>), 62 | [start_array,start_array,1,end_array,parse_buf] = jstream_test:all_events(<<" [[1] ">>), 63 | [start_array,1,start_array,2,start_array,end_array,end_array,start_array,start_array,end_array,4,end_array, 64 | end_array,{parse_end,<<>>}] = jstream_test:all_events(<<"[1,[2,[]], [[],4]]">>). 65 | 66 | object_test() -> 67 | [start_map,end_map,{parse_end,<<>>}] = jstream_test:all_events(<<"{}">>), 68 | [start_map,{map_key,<<"k">>},1,end_map,{parse_end,<<>>}] = jstream_test:all_events(<<"{\"k\": 1}">>), 69 | [start_map,{map_key,<<"k1">>},1,{map_key,<<"k2">>},2,end_map,{parse_end,<<>>}] = 70 | jstream_test:all_events(<<"{\"k1\": 1, \"k2\": 2}">>), 71 | [start_map,{map_key,<<"k1">>},1,{map_key,<<"k2">>},2,{map_key,<<"k3">>},3,end_map,{parse_end,<<>>}] = 72 | jstream_test:all_events(<<"{\"k1\" :1, \"k2\": 2, \"k3\" : 3}">>), 73 | [start_map,{map_key,<<"k1">>},1,{map_key,<<"k2">>},2,{error,invalid_json}] = jstream_test:all_events(<<"{\"k1\": 1, \"k2\": 2]">>), 74 | [start_map,{map_key,<<"k1">>},1,{error,invalid_json}] = jstream_test:all_events(<<"{\"k1\": 1 \"k2\": 2]">>). 75 | 76 | complex_test() -> 77 | [start_array, 78 | start_map,end_map, 79 | start_map,{map_key,<<"k0">>},true,end_map, 80 | start_map,{map_key,<<"k1">>},start_array,11,1.1,start_map,{map_key,<<"k3">>},3,end_map,end_array,end_map, 81 | start_map,{map_key,<<"superkey">>},start_map,{map_key,<<"subkey">>},null,end_map,end_map, 82 | end_array,{parse_end,<<"...rest">>}] = 83 | jstream_test:all_events(<<"[{}, {\"k0\": true}, {\"k1\": [11, 1.1, {\"k3\":3}]}, {\"superkey\": {\"subkey\":null}}] ...rest">>). 84 | 85 | error_test() -> 86 | [{error,invalid_json}] = jstream_test:all_events(<<",">>), 87 | [{error,big_num}] = jstream_test:all_events(<<"12345678901234567890">>), 88 | [{error,invalid_string}] = jstream_test:all_events(<<"\"",1,127,"\"">>). 89 | 90 | list_chunks_test() -> 91 | [start_array,1,22,parse_buf,333,parse_buf,444,parse_buf,5111,parse_buf,5,start_array,parse_buf,end_array,end_array,{parse_end,<<>>}] 92 | = jstream_test:all_events_from_blist([<<"[1, 22,">>, <<"333">>, <<",444">>, <<",5111,">>, <<"5, [">>,<<"]]">>]), 93 | [start_array,parse_buf,1234567,parse_buf,parse_buf, 94 | 234567890123456789,parse_buf,start_array,parse_buf, 95 | end_array,parse_buf,end_array, {parse_end,<<>>}] 96 | = jstream_test:all_events_from_blist([<<"[">>, <<"1234567">>, <<",">>, <<"234567890123456789,">>, <<"[">>,<<"]">>, <<"]">>]). 97 | 98 | obj_chunks_test() -> 99 | [start_map,parse_buf,{map_key,<<"k1">>},parse_buf,true,parse_buf,{map_key,<<"k2">>}, 100 | parse_buf,false,parse_buf,end_map,{parse_end,<<>>}] 101 | = jstream_test:all_events_from_blist([<<"{">>,<<"\"k1\":">>, <<"true , ">>, <<"\"k2\":">>,<<"false">>,<<"}">>]), 102 | [start_map,parse_buf,{map_key,<<"k1">>},parse_buf,true,parse_buf,end_map,{parse_end,<<>>}] 103 | = jstream_test:all_events_from_blist([<<"{">>,<<"\"k1\":">>, <<"true">>,<<"}">>]), 104 | [start_map,parse_buf,{map_key,<<"k1">>},parse_buf,true,parse_buf,parse_buf, 105 | {map_key,<<"k2">>},parse_buf,false,parse_buf,end_map,{parse_end,<<>>}] 106 | = jstream_test:all_events_from_blist([<<"{">>,<<"\"k1\":">>, <<"true">>, <<",">>, <<"\"k2\":">>,<<"false">>,<<"}">>]), 107 | [start_map,{map_key,<<"key">>},parse_buf,start_array,1234567,2345,parse_buf,start_map, 108 | parse_buf,end_map,parse_buf,end_array,end_map,{parse_end,<<>>}] 109 | = jstream_test:all_events_from_blist([<<"{\"key\":">>, <<"[1234567,2345,">>, <<"{">>,<<"}">>, <<"]}">>]). 110 | 111 | obj_chunks_err_test() -> 112 | [start_map,parse_buf,{map_key,<<"k1">>},parse_buf,true,parse_buf,{error,invalid_json}] 113 | = jstream_test:all_events_from_blist([<<"{">>,<<"\"k1\":">>, <<"true ,">>, <<",\"k2\":">>,<<"false">>,<<"}">>]). 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | JSONX is an Erlang library for efficient JSON decoding and encoding, implemented in Erlang NIFs. 3 | Works with binaries as strings, arrays as lists and only knows how to decode UTF-8 (and ASCII). 4 | 5 | JSONX IS VERY FAST! 6 | ------------------ 7 | 8 | Check out a benchmark [si14/erl_json_test](https://github.com/si14/erl_json_test) or 9 | [davisp/erljson_bench](https://github.com/davisp/erljson_bench) and 10 | record encoding tests in `/test/bench_encode_records.erl` 11 | 12 | INSTALLATION and DOCUMENTATION 13 | ------------------------------ 14 | 15 | * cd jsonx 16 | * make 17 | * make doc 18 | * firefox doc/index.html& 19 | 20 | JSONX can encode and decode Erlang records! 21 | ------------------------------------------- 22 | 23 | ```erlang 24 | -module(record_example). 25 | -compile(export_all). 26 | 27 | -record(person, {name, age, friends}). 28 | -record(person2, {name, age, phone}). 29 | 30 | encoder1() -> 31 | jsonx:encoder([{person, record_info(fields, person)}, 32 | {person2, record_info(fields, person2)} ]). 33 | 34 | decoder1() -> 35 | jsonx:decoder([{person, record_info(fields, person)}, 36 | {person2, record_info(fields, person2)}]). 37 | 38 | nonstrict_decoder1() -> 39 | jsonx:decoder([{person, record_info(fields, person)}, 40 | {person2, record_info(fields, person2)}], 41 | [{format, proplist}]). 42 | ``` 43 | 44 | ```erlang 45 | 1> c(records_examples). 46 | {ok,records_examples} 47 | 48 | 2> rr(record_examples). 49 | [person,person2] 50 | 51 | 3> BabaYaga = #person2{name = <<"BabaYaga">>, age = 118, phone = <<"666-66-66">>}. 52 | #person2{name = <<"BabaYaga">>,age = 118, 53 | phone = <<"666-66-66">>} 54 | 55 | 4> Vasya = #person{name = <<"Vasya">>, age = 18, friends = [BabaYaga]}. 56 | #person{name = <<"Vasya">>,age = 18, 57 | friends = [#person2{name = <<"BabaYaga">>,age = 118, 58 | phone = <<"666-66-66">>}]} 59 | 60 | 5> Encoder = record_examples:encoder1(). 61 | #Fun 62 | 63 | 6> Decoder = record_examples:decoder1(). 64 | #Fun 65 | 66 | 7> Json = Encoder(BabaYaga). 67 | <<"{\"name\": \"BabaYaga\",\"age\": 118,\"phone\": \"666-66-66\"}">> 68 | 69 | 8> Decoder(Json). 70 | #person2{name = <<"BabaYaga">>,age = 118, 71 | phone = <<"666-66-66">>} 72 | 9> Json2 = Encoder(Vasya). 73 | <<"{\"name\": \"Vasya\",\"age\": 18,\"friends\": [{\"name\": \"BabaYaga\",\"age\": 118,\"phone\": \"666-66-66\"}]}">> 74 | 75 | 10> Decoder(Json2). 76 | #person{name = <<"Vasya">>,age = 18, 77 | friends = [#person2{name = <<"BabaYaga">>,age = 118, 78 | phone = <<"666-66-66">>}]} 79 | 80 | 11> Json3 = <<"[{\"name\": \"BabaYaga\",\"age\": 118,\"phone\": \"666-66-66\"}, {\"record\": \"undefined\", \"strict\": false}]">>. 81 | <<"[{\"name\": \"BabaYaga\",\"age\": 118,\"phone\": \"666-66-66\"}, {\"record\": \"undefined\", \"strict\": false}]">> 82 | 83 | 12> Decoder(Json3). 84 | {error,undefined_record,64} 85 | 86 | 13> NonStrictDecoder = record_examples:nonstrict_decoder1(). 87 | #Fun 88 | 89 | 14> JTerm = NonStrictDecoder(Json3). 90 | [#person2{name = <<"BabaYaga">>,age = 118, 91 | phone = <<"666-66-66">>}, 92 | [{<<"record">>,<<"undefined">>},{<<"strict">>,false}]] 93 | 94 | 15> Encoder(JTerm). 95 | <<"[{\"name\": \"BabaYaga\",\"age\": 118,\"phone\": \"666-66-66\"},{\"record\":\"undefined\",\"strict\":false}]">> 96 | ``` 97 | 98 | 99 | Examples encoding JSON 100 | ---------------------- 101 | 102 | ```erlang 103 | 1> jsonx:encode([1, 2.3, true, false, null, atom, <<"string">>, []]). 104 | <<"[1,2.3,true,false,null,\"atom\",\"string\",[]]">> 105 | 106 | %% Object as proplist 107 | 2> jsonx:encode( [{name, <<"Ivan">>}, {age, 33}, {phones, [3332211, 4443322]}] ). 108 | <<"{\"name\":\"Ivan\",\"age\":33,\"phones\":[3332211,4443322]}">> 109 | 110 | %% Object as struct 111 | 3> jsonx:encode( {struct, [{name, <<"Ivan">>}, {age, 33}, {phones, [3332211, 4443322]}]} ). 112 | <<"{\"name\":\"Ivan\",\"age\":33,\"phones\":[3332211,4443322]}">> 113 | 114 | %% Object as eep18 propsal 115 | 4> jsonx:encode( {[{name, <<"Ivan">>}, {age, 33}, {phones, [3332211, 4443322]}]} ). 116 | <<"{\"name\":\"Ivan\",\"age\":33,\"phones\":[3332211,4443322]}">> 117 | ``` 118 | 119 | Examples decoding JSON 120 | ---------------------- 121 | 122 | ```erlang 123 | 1> jsonx:decode(<<"{\"name\":\"Ivan\",\"age\":33,\"phones\":[3332211,4443322]}">>). 124 | {[{<<"name">>,<<"Ivan">>}, 125 | {<<"age">>,33}, 126 | {<<"phones">>,[3332211,4443322]}]} 127 | 128 | 2> jsonx:decode(<<"{\"name\":\"Ivan\",\"age\":33,\"phones\":[3332211,4443322]}">>, [{format, eep18}]). 129 | {[{<<"name">>,<<"Ivan">>}, 130 | {<<"age">>,33}, 131 | {<<"phones">>,[3332211,4443322]}]} 132 | 133 | 3> jsonx:decode(<<"{\"name\":\"Ivan\",\"age\":33,\"phones\":[3332211,4443322]}">>, [{format, proplist}]). 134 | [{<<"name">>,<<"Ivan">>}, 135 | {<<"age">>,33}, 136 | {<<"phones">>,[3332211,4443322]}] 137 | 138 | 4> jsonx:decode(<<"{\"name\":\"Ivan\",\"age\":33,\"phones\":[3332211,4443322]}">>, [{format, struct}]). 139 | {struct,[{<<"name">>,<<"Ivan">>}, 140 | {<<"age">>,33}, 141 | {<<"phones">>,[3332211,4443322]}]} 142 | ``` 143 | 144 | Example streaming parse 145 | ----------------------- 146 | 147 | More example see `examples/stream_example.erl` . 148 | 149 | ```erlang 150 | 1> D = jstream:new_decoder(<<"{\"key1\": \"val1\",\n">>). 151 | <<>> 152 | 153 | 2> jstream:get_event(D). 154 | start_map 155 | 156 | 3> jstream:get_event(D). 157 | {map_key,<<"key1">>} 158 | 159 | 4> jstream:get_event(D). 160 | <<"val1">> 161 | 162 | 5> jstream:get_event(D). 163 | parse_buf 164 | 165 | 6> ok = jstream:update_decoder(D, <<"\"key2\": \"val2\"}\n">>). 166 | ok 167 | 168 | 7> jstream:get_event(D). 169 | {map_key,<<"key2">>} 170 | 171 | 8> jstream:get_event(D). 172 | <<"val2">> 173 | 174 | 9> jstream:get_event(D). 175 | end_map 176 | 177 | 10> jstream:get_event(D). 178 | {parse_end,<<>>} 179 | 180 | ``` 181 | 182 | Mapping (JSON -> Erlang) 183 | ---------------------- 184 | 185 | null :-> null 186 | true :-> true 187 | false :-> false 188 | "string" :-> <<"binary">> 189 | [1, 2.3, []] :-> [1, 2.3, []] 190 | {"this": "json"} :-> {[{<<"this">>: <<"json">>}]} %% default eep18 191 | {"this": "json"} :-> [{<<"this">>: <<"json">>}] %% optional proplist 192 | {"this": "json"} :-> {struct, [{<<"this">>: <<"json">>}]} %% optional struct 193 | JSONObject :-> #rec{...} %% decoder must be predefined 194 | 195 | Mapping (Erlang -> JSON) 196 | ----------------------- 197 | 198 | null :-> null 199 | true :-> true 200 | false :-> false 201 | atom :-> "atom" 202 | <<"str">> :-> "str" 203 | [1, 2.99] :-> [1, 2.99] 204 | {struct, [{<<"this">>: <<"json">>}]} :-> {"this": "json"} 205 | [{<<"this">>: <<"json">>}] :-> {"this": "json"} 206 | {[{<<"this">>: <<"json">>}]} :-> {"this": "json"} 207 | {json, IOList} :-> `iolist_to_binary(IOList)` %% include with no validation 208 | #rec{...} :-> JSONObject %% encoder must be predefined 209 | -------------------------------------------------------------------------------- /c_src/jsonx.c: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Yuriy Iskra 2 | 3 | #include 4 | #include "jsonx.h" 5 | 6 | static void 7 | enc_rt_dtor(ErlNifEnv* env, void* obj){ 8 | EncEntry *entry = (EncEntry*)obj; 9 | enif_release_binary(&entry->bin); 10 | entry->bin.data = NULL; 11 | if(entry->ignored){ 12 | enif_free(entry->ignored); 13 | entry->ignored = NULL; 14 | } 15 | entry = NULL; 16 | } 17 | 18 | static int 19 | load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info){ 20 | 21 | PrivData *pdata = enif_alloc(sizeof(PrivData)); 22 | if(pdata == NULL) return 1; 23 | 24 | pdata->encoder_RSTYPE = enif_open_resource_type(env, NULL, 25 | "encoder_RSTYPE", 26 | enc_rt_dtor, 27 | ERL_NIF_RT_CREATE, NULL); 28 | if (pdata->encoder_RSTYPE == NULL) return 1; 29 | 30 | pdata->decoder_RSTYPE = enif_open_resource_type(env, NULL, 31 | "decoder_RSTYPE", 32 | NULL, 33 | ERL_NIF_RT_CREATE, NULL); 34 | if (pdata->decoder_RSTYPE == NULL) return 1; 35 | 36 | if(!enif_make_existing_atom(env, "true", &(pdata->am_true), ERL_NIF_LATIN1)) return 1; 37 | if(!enif_make_existing_atom(env, "false", &(pdata->am_false), ERL_NIF_LATIN1)) return 1; 38 | if(!enif_make_existing_atom(env, "null", &(pdata->am_null), ERL_NIF_LATIN1)) return 1; 39 | 40 | if(!enif_make_existing_atom(env, "error", &(pdata->am_error), ERL_NIF_LATIN1)) return 1; 41 | if(!enif_make_existing_atom(env, "big_num", &(pdata->am_erange), ERL_NIF_LATIN1)) return 1; 42 | if(!enif_make_existing_atom(env, "invalid_string", &(pdata->am_estr), ERL_NIF_LATIN1)) return 1; 43 | if(!enif_make_existing_atom(env, "invalid_json", &(pdata->am_esyntax), ERL_NIF_LATIN1)) return 1; 44 | if(!enif_make_existing_atom(env, "trailing_data", &(pdata->am_etrailing), ERL_NIF_LATIN1)) return 1; 45 | if(!enif_make_existing_atom(env, "undefined_record", &(pdata->am_undefined_record),ERL_NIF_LATIN1)) return 1; 46 | 47 | if(!enif_make_existing_atom(env, "json", &(pdata->am_json), ERL_NIF_LATIN1)) return 1; 48 | if(!enif_make_existing_atom(env, "struct", &(pdata->am_struct), ERL_NIF_LATIN1)) return 1; 49 | if(!enif_make_existing_atom(env, "proplist", &(pdata->am_proplist), ERL_NIF_LATIN1)) return 1; 50 | if(!enif_make_existing_atom(env, "eep18", &(pdata->am_eep18), ERL_NIF_LATIN1)) return 1; 51 | if(!enif_make_existing_atom(env, "no_match", &(pdata->am_no_match), ERL_NIF_LATIN1)) return 1; 52 | 53 | *priv_data = (void*)pdata; 54 | 55 | return 0; 56 | } 57 | 58 | static int 59 | reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info){ 60 | return 0; 61 | } 62 | 63 | static int 64 | upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info){ 65 | *priv_data = *old_priv_data; 66 | return 0; 67 | } 68 | static void 69 | unload(ErlNifEnv* env, void* priv_data){ 70 | enif_free(priv_data); 71 | return; 72 | } 73 | 74 | ERL_NIF_TERM 75 | make_encoder_resource_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){ 76 | unsigned rs_len, fs_len, bin_sz; 77 | enif_get_uint(env, argv[0], &rs_len); 78 | enif_get_uint(env, argv[1], &fs_len); 79 | enif_get_uint(env, argv[4], &bin_sz); 80 | PrivData* priv = (PrivData*)enif_priv_data(env); 81 | unsigned resource_sz = enc_resource_size(rs_len, fs_len); 82 | EncEntry *enc_entry = (EncEntry*)enif_alloc_resource(priv->encoder_RSTYPE, resource_sz); 83 | //memset(enc_entry, 0, resource_sz); 84 | enc_entry->records_cnt = rs_len; 85 | enc_entry->fields_cnt = fs_len; 86 | if(!enif_alloc_binary(bin_sz + 1, &enc_entry->bin)) 87 | goto error; 88 | //memset(enc_entry->bin.data, 0, bin_sz + 1); 89 | ErlNifBinary ebin; 90 | enif_inspect_binary(env, argv[5], &ebin); 91 | memcpy(enc_entry->bin.data, ebin.data , ebin.size); 92 | 93 | ERL_NIF_TERM list, head, tail; 94 | list = argv[2]; 95 | int i = 0; 96 | while(enif_get_list_cell(env, list, &head, &tail)){ 97 | const ERL_NIF_TERM *tuple; 98 | int arity; 99 | unsigned ip; 100 | enif_get_tuple(env, head, &arity, &tuple); 101 | EncRecord *records = enc_records_base(enc_entry); 102 | records[i].tag = tuple[0]; 103 | enif_get_uint(env, tuple[1], &ip); 104 | records[i].fds_offset = ip; 105 | enif_get_uint(env, tuple[2], &ip); 106 | records[i].arity = ip; 107 | i++; 108 | list = tail; 109 | } 110 | 111 | list = argv[3]; 112 | i = 0; 113 | while(enif_get_list_cell(env, list, &head, &tail)){ 114 | const ERL_NIF_TERM *tuple; 115 | int arity; 116 | unsigned ip; 117 | enif_get_tuple(env, head, &arity, &tuple); 118 | EncField *fields = enc_fields_base(enc_entry); 119 | enif_get_uint(env, tuple[0], &ip); 120 | fields[i].offset = ip; 121 | enif_get_uint(env, tuple[1], &ip); 122 | fields[i].size = ip; 123 | i++; 124 | list = tail; 125 | } 126 | 127 | list = argv[6]; 128 | if(!enif_get_list_length(env, list, &(enc_entry->ignored_len))) 129 | goto error; 130 | enc_entry->ignored = (ERL_NIF_TERM*)enif_alloc(enc_entry->ignored_len*sizeof(ERL_NIF_TERM)); 131 | i = 0; 132 | while(enif_get_list_cell(env, list, &head, &tail)){ 133 | // ignored term should be atoms 134 | if(enif_is_atom(env, head)){ 135 | enc_entry->ignored[i] = head; 136 | }else{ 137 | enif_free(enc_entry->ignored); 138 | goto error; 139 | } 140 | i++; 141 | list = tail; 142 | } 143 | ERL_NIF_TERM ret = enif_make_resource(env, (void *)enc_entry); 144 | enif_release_resource(enc_entry); 145 | return ret; 146 | error: 147 | enif_release_resource(enc_entry); 148 | return enif_make_badarg(env); 149 | } 150 | 151 | ERL_NIF_TERM 152 | make_decoder_resource_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){ 153 | unsigned records_cnt, ukeys_cnt, keys_cnt; 154 | 155 | enif_get_uint(env, argv[0], &records_cnt); 156 | enif_get_uint(env, argv[1], &ukeys_cnt); 157 | enif_get_uint(env, argv[2], &keys_cnt); 158 | PrivData* priv = (PrivData*)enif_priv_data(env); 159 | unsigned resource_sz = dec_resource_size(records_cnt, ukeys_cnt, keys_cnt); 160 | DecEntry *dec_entry = (DecEntry*)enif_alloc_resource(priv->decoder_RSTYPE, resource_sz); 161 | 162 | dec_entry->records_cnt = records_cnt; 163 | dec_entry->ukeys_cnt = ukeys_cnt; 164 | dec_entry->keys_cnt = keys_cnt; 165 | 166 | ERL_NIF_TERM list, head, tail; 167 | int i; 168 | ERL_NIF_TERM* ukeys = ukeys_base(dec_entry); 169 | list = argv[3]; //UKeys 170 | for(i = 0; i < ukeys_cnt; i++){ 171 | if(!enif_get_list_cell(env, list, &head, &tail)) 172 | goto error; 173 | ukeys[i] = head; 174 | list = tail; 175 | } 176 | 177 | unsigned* keys = keys_base(dec_entry, ukeys_cnt); 178 | list = argv[4]; //KeyNums 179 | for(i = 0; i < keys_cnt; i++){ 180 | if(!enif_get_list_cell(env, list, &head, &tail)) 181 | goto error; 182 | if(!enif_get_uint(env, head, &keys[i])) 183 | goto error; 184 | list = tail; 185 | } 186 | 187 | DecRecord* records = records_base(dec_entry, ukeys_cnt, keys_cnt); 188 | long *bit_mask = bit_mask_base(dec_entry, ukeys_cnt, keys_cnt, records_cnt); 189 | size_t bit_mask_len = BITS_TO_WORDS(dec_entry->ukeys_cnt); 190 | memset((void *)bit_mask, 0, bit_mask_array_size(ukeys_cnt, records_cnt)); 191 | const ERL_NIF_TERM *tuple; 192 | int arity, k; 193 | list = argv[5]; //Records 194 | for(i = 0; i < records_cnt; i++){ 195 | if(!enif_get_list_cell(env, list, &head, &tail)) 196 | goto error; 197 | if(!(enif_get_tuple(env, head, &arity, &tuple) && (arity == 3))) 198 | goto error; 199 | if(!enif_is_atom(env, tuple[0])) 200 | goto error; 201 | records[i].tag = tuple[0]; 202 | if(!enif_get_uint(env, tuple[1], &records[i].keys_off)) 203 | goto error; 204 | if(!enif_get_uint(env, tuple[2], &records[i].arity)) 205 | goto error; 206 | list = tail; 207 | 208 | for(k = 0; k < records[i].arity; k++){ 209 | int p = records[i].keys_off + k; //position in keys 210 | set_bit(keys[p], bit_mask + (i * bit_mask_len)); 211 | } 212 | } 213 | 214 | ERL_NIF_TERM ret = enif_make_resource(env, (void *)dec_entry); 215 | enif_release_resource(dec_entry); 216 | return ret; 217 | error: 218 | enif_release_resource(dec_entry); 219 | return enif_make_badarg(env); 220 | } 221 | 222 | static ErlNifFunc 223 | nif_funcs[] = { 224 | {"encode1", 1, encode_nif}, 225 | {"encode_res", 2, encode_nif}, // with resource 226 | {"decode_opt", 2, decode_nif}, // with options 227 | {"decode_res", 4, decode_nif}, // with options, resource and strict flag 228 | {"make_encoder_resource", 7, make_encoder_resource_nif}, 229 | {"make_decoder_resource", 6, make_decoder_resource_nif} 230 | }; 231 | 232 | ERL_NIF_INIT(jsonx, nif_funcs, load, reload, upgrade, unload); 233 | 234 | -------------------------------------------------------------------------------- /src/jsonx.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013 Yuriy Iskra 2 | 3 | %% @doc JSONX is an Erlang library for efficient decode and encode JSON, written in C. 4 | %% Works with binaries as strings, arrays as lists and it only knows how to decode UTF-8 (and ASCII). 5 | %% 6 | %%

Decode (json -> erlang)

7 | %%
    8 | %%
  • null -> atom null
  • 9 | %%
  • true -> atom true
  • 10 | %%
  • false -> atom false
  • 11 | %%
  • string -> binary
  • 12 | %%
  • number -> number
  • 13 | %%
  • array -> list
  • 14 | %%
  • object -> {PropList}, optional struct or proplist.
  • 15 | %%
  • object -> #record{...} - decoder must be predefined
  • 16 | %%
17 | %%

Encode (erlang -> json)

18 | %%
    19 | %%
  • atom null -> null
  • 20 | %%
  • atom true -> true
  • 21 | %%
  • atom true -> false
  • 22 | %%
  • any other atom -> string
  • 23 | %%
  • binary -> string
  • 24 | %%
  • number -> number
  • 25 | %%
  • {struct, PropList} -> object
  • 26 | %%
  • {PropList} -> object
  • 27 | %%
  • PropList -> object
  • 28 | %%
  • #record{...} -> object - encoder must be predefined
  • 29 | %%
  • {json, IOList} -> include IOList with no validation
  • 30 | %%
31 | 32 | -module(jsonx). 33 | -export([encode/1, decode/1, decode/2, 34 | encoder/1, encoder/2, decoder/1, decoder/2]). 35 | -on_load(init/0). 36 | -define(LIBNAME, jsonx). 37 | -define(APPNAME, jsonx). 38 | 39 | %% ================= 40 | %% API Encoding JSON 41 | %% ================= 42 | 43 | %%@doc Encode JSON. 44 | -spec encode(JSON_TERM) -> JSON when 45 | JSON :: binary(), 46 | JSON_TERM :: any(). 47 | encode(JSON_TERM)-> 48 | encode1(JSON_TERM). 49 | 50 | %%@doc Build a JSON encoder. 51 | -spec encoder(RECORDS_DESC) -> ENCODER when 52 | RECORDS_DESC :: [{tag, [names]}], 53 | ENCODER :: function(). 54 | encoder(Records_desc) -> 55 | encoder(Records_desc, []). 56 | 57 | %%@doc Build a JSON encoder. 58 | -spec encoder(RECORDS_DESC, OPTIONS) -> ENCODER when 59 | RECORDS_DESC :: [{tag, [names]}], 60 | OPTIONS :: [{ignore, [atom()]}], 61 | ENCODER :: function(). 62 | encoder(Records_desc, Options) -> 63 | {Rcnt, Fcnt, Binsz, Records, Fields, Bin} = prepare_enc_desc(Records_desc), 64 | Ignored = proplists:get_value(ignore, Options, []), 65 | Resource = make_encoder_resource(Rcnt, Fcnt, Records, Fields, Binsz, Bin, Ignored), 66 | fun(JSON_TERM) -> encode_res(JSON_TERM, Resource) end. 67 | 68 | %% ================== 69 | %% API Decoding JSON 70 | %% ================== 71 | 72 | %%@doc Decode JSON to Erlang term. 73 | -spec decode(JSON) -> JSON_TERM when 74 | JSON :: binary(), 75 | JSON_TERM :: any(). 76 | decode(JSON) -> 77 | decode_opt(JSON, eep18). 78 | 79 | %%@doc Decode JSON to Erlang term with options. 80 | -spec decode(JSON, OPTIONS) -> JSON_TERM when 81 | JSON :: binary(), 82 | OPTIONS :: [{format, struct|eep18|proplist}], 83 | JSON_TERM :: any(). 84 | decode(JSON, Options) -> 85 | case parse_format(Options) of 86 | undefined -> decode_opt(JSON, eep18); 87 | F -> decode_opt(JSON, F) 88 | end. 89 | 90 | %%@doc Build a JSON decoder. 91 | -spec decoder(RECORDS_DESC) -> DECODER when 92 | RECORDS_DESC :: [{tag, [names]}], 93 | DECODER :: function(). 94 | decoder(Records_desc) -> 95 | {RecCnt, UKeyCnt, KeyCnt, UKeys, Keys, Records3} = prepare_for_dec(Records_desc), 96 | Resource = make_decoder_resource(RecCnt, UKeyCnt, KeyCnt, UKeys, Keys, Records3), 97 | fun(JSON_TERM) -> decode_res(JSON_TERM, eep18, Resource, true) end. 98 | 99 | %%@doc Build a JSON decoder with output undefined objects. 100 | -spec decoder(RECORDS_DESC, OPTIONS) -> DECODER when 101 | RECORDS_DESC :: [{tag, [names]}], 102 | OPTIONS :: [{format, struct|eep18|proplist}], 103 | DECODER :: function(). 104 | decoder(Records_desc, Options) -> 105 | {RecCnt, UKeyCnt, KeyCnt, UKeys, Keys, Records3} = prepare_for_dec(Records_desc), 106 | Resource = make_decoder_resource(RecCnt, UKeyCnt, KeyCnt, UKeys, Keys, Records3), 107 | %%Format = parse_format(Options), 108 | case parse_format(Options) of 109 | undefined -> fun(JSON_TERM) -> decode_res(JSON_TERM, eep18, Resource, false) end; 110 | Format -> fun(JSON_TERM) -> decode_res(JSON_TERM, Format, Resource, false) end 111 | end. 112 | 113 | %% ========== 114 | %% Call NIFs 115 | %% ========== 116 | 117 | encode1(_JSON_TERM) -> 118 | not_loaded(?LINE). 119 | 120 | encode_res(_JSON_TERM, _RESOURCE) -> 121 | not_loaded(?LINE). 122 | 123 | decode_opt(_JSON, _FORMAT) -> 124 | not_loaded(?LINE). 125 | 126 | decode_res(_JSON_TERM, _FORMAT, _RESOURCE, _STRICT_FLAG) -> 127 | not_loaded(?LINE). 128 | 129 | make_encoder_resource(_Rcnt, _Fcnt, _Records, _Fields, _Binsz, _Bin, _Ignored) -> 130 | not_loaded(?LINE). 131 | 132 | make_decoder_resource(_RecCnt, _UKeyCnt, _KeyCnt, _UKeys, _Keys, _Records3) -> 133 | not_loaded(?LINE). 134 | 135 | %% ================= 136 | %% Private functions 137 | %% ================= 138 | 139 | parse_format([]) -> 140 | undefined; 141 | parse_format([{format, struct} | _]) -> 142 | struct; 143 | parse_format([{format, proplist} | _]) -> 144 | proplist; 145 | parse_format([{format, eep18} | _]) -> 146 | eep18; 147 | parse_format([_H | T]) -> 148 | parse_format(T). 149 | 150 | %%%% Internal for decoder 151 | 152 | prepare_for_dec(Records) -> 153 | RecCnt = length(Records), 154 | Records1 = lists:ukeysort(1,Records), 155 | RecCnt1 = length(Records1), 156 | case (RecCnt1 == RecCnt) of 157 | true -> 158 | UKeys = lists:usort(lists:flatten([Ks || {_Tag, Ks} <- Records1])), 159 | {UKeyCnt, EnumUKeys} = enumerate(UKeys), 160 | Records2 = [{Tag, length(Keys), [ findpos(EnumUKeys, K) || K <- Keys] } || {Tag, Keys} <- Records1], 161 | {KeyCnt, Records3, Keys} = scan_records(Records2), 162 | { RecCnt %% Records Counter 163 | , UKeyCnt %% Uniq Keys Counter 164 | , KeyCnt %% Keys Counter 165 | , UKeys %% [Key] 166 | , Keys %% [KeyNum] 167 | , Records3 %% [{Tag, Off, Len}] 168 | }; 169 | false -> 170 | {error, invalid_input} 171 | end. 172 | 173 | scan_records(Records2) -> 174 | scan_records({0, [], []}, Records2). 175 | scan_records({Offs, AccR, AccK}, []) -> 176 | {Offs, lists:reverse(AccR), lists:reverse(AccK)}; 177 | scan_records({Offs, AccR, AccK}, [{Tag, Len, KeyNums} | Ts]) -> 178 | scan_records({Offs + Len, [{Tag, Offs, Len} | AccR], lists:reverse(KeyNums, AccK)}, Ts). 179 | 180 | findpos(EnumKeys, Key) -> 181 | {Num, _Key} = lists:keyfind(Key, 2, EnumKeys), 182 | Num. 183 | 184 | enumerate(Xs) -> 185 | enumerate(0, [], Xs ). 186 | enumerate(N, Acc, []) -> 187 | {N, lists:reverse(Acc)}; 188 | enumerate(N, Acc, [H| Ts]) -> 189 | enumerate(N + 1, [{N, H} | Acc], Ts). 190 | 191 | %%%% Internal for encoder 192 | 193 | prepare_enc_desc(T) -> 194 | {Rcnt, Fcnt, Records, {Fields, Blen, Bins}} = prepare_enc_desc1(T), 195 | {Rcnt, Fcnt, Blen, lists:reverse(Records), lists:reverse(Fields), 196 | iolist_to_binary(lists:reverse(Bins))}. 197 | prepare_enc_desc1(Records) -> 198 | prepare_enc_desc2(Records, {_Rcnt = 0, _OffF = 0, _Records = [], 199 | {_Fields = [], _OffB = 0, _Bins = []}}). 200 | prepare_enc_desc2([], R) -> 201 | R; 202 | prepare_enc_desc2([{Tag, Fields} | RTail], {Rcnt, OffF, Records, FieldsR}) when is_atom(Tag) -> 203 | Fcnt = length(Fields), 204 | prepare_enc_desc2(RTail, {Rcnt+1, OffF + Fcnt, [{Tag, OffF, Fcnt} | Records] , prepare_enc_fields1(Fields, FieldsR)}). 205 | prepare_enc_fields1([], R) -> 206 | R; 207 | prepare_enc_fields1( [Name|NTail], {Fields, OffB, Bins} ) when is_atom(Name) -> 208 | Bin = iolist_to_binary(["\"", atom_to_binary(Name, latin1),<<"\": ">>]), 209 | LenB = size(Bin), 210 | prepare_enc_fields(NTail, {[{OffB, LenB} | Fields], OffB + LenB, [Bin|Bins]}). 211 | prepare_enc_fields([], R) -> 212 | R; 213 | prepare_enc_fields( [Name|NTail], {Fields, OffB, Bins} ) when is_atom(Name) -> 214 | Bin = iolist_to_binary([",\"", atom_to_binary(Name, latin1),<<"\": ">>]), 215 | LenB = size(Bin), 216 | prepare_enc_fields(NTail, {[{OffB, LenB} | Fields], OffB + LenB, [Bin|Bins]}). 217 | 218 | %% Init 219 | 220 | init() -> 221 | So = case code:priv_dir(?APPNAME) of 222 | {error, bad_name} -> 223 | case filelib:is_dir(filename:join(["..", priv])) of 224 | true -> 225 | filename:join(["..", priv, ?LIBNAME]); 226 | _ -> 227 | filename:join([priv, ?LIBNAME]) 228 | end; 229 | Dir -> 230 | filename:join(Dir, ?LIBNAME) 231 | end, 232 | ok = erlang:load_nif(So, [[json, struct, proplist, eep18, no_match], [true, false, null], 233 | [error, big_num, invalid_string, invalid_json, trailing_data, undefined_record]]). 234 | 235 | not_loaded(Line) -> 236 | erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). 237 | -------------------------------------------------------------------------------- /c_src/jsonx_str.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Yuriy Iskra 2 | 3 | #ifdef _MSC_VER 4 | #define inline __inline 5 | #endif 6 | 7 | //2-byte utf8 seq(110xxxxx) 8 | #define U2 (1<<0) 9 | //3-byte utf8 seq(1110xxxx) 10 | #define U3 (1<<1) 11 | //4-byte utf8 seq(11110xxxx) 12 | #define U4 (1<<2) 13 | //Utf8 rest byte(10xxxxxx), or bad start sequence 14 | #define R (1<<3) 15 | //Not valid Utf8 byte 16 | #define B (1<<4) 17 | //Char escaped to \X 18 | // 8-13:"\b\t\n\v\f\r", 34:'"', 92:'\\' 19 | #define E2 (1<<5) 20 | //Byte escaped to \uXXXX, 0-31 without E2 and 127 21 | #define E6 (1<<6) 22 | #define E2E6 (E2|E6) 23 | 24 | static const char hexval[] = "0123456789abcdef"; 25 | 26 | static const unsigned char js_map[] = { 27 | // 0 1 2 3 4 5 6 7 8 9 a b c d e f 28 | 29 | E6, E6, E6, E6, E6, E6, E6, E6, E2, E2, E2, E2, E2, E2, E6, E6, 30 | E6, E6, E6, E6, E6, E6, E6, E6, E6, E6, E6, E6, E6, E6, E6, E6, 31 | 0, 0, E2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, E2, 0, 0, 0, 36 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, E6, 38 | 39 | R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, 40 | R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, 41 | R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, 42 | R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, R, 43 | 44 | /* 110xxxxx C0..DF */ 45 | U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, 46 | U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, U2, 47 | /* 1110xxxx E0..EF */ 48 | U3, U3, U3, U3, U3, U3, U3, U3, U3, U3, U3, U3, U3, U3, U3, U3, 49 | /* 11110xxx F0..F7 */ 50 | U4, U4, U4, U4, U4, U4, U4, U4, B, B, B, B, B, B, B, B 51 | }; 52 | 53 | #define MINUS1 0xFF 54 | #define M1 MINUS1 55 | 56 | static const unsigned char hex_tab[256] = { 57 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 58 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 59 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 60 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, M1, M1, M1, M1, M1, M1, 61 | M1, 10, 11, 12, 13, 14, 15, M1, M1, M1, M1, M1, M1, M1, M1, M1, 62 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 63 | M1, 10, 11, 12, 13, 14, 15, M1, M1, M1, M1, M1, M1, M1, M1, M1, 64 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 65 | 66 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 67 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 68 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 69 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 70 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 71 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 72 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 73 | M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, M1, 74 | }; 75 | 76 | static inline int 77 | ucs_from_4hex(unsigned char* ptr, unsigned* hval ){ 78 | unsigned char h; 79 | 80 | h = hex_tab[*(ptr)]; 81 | if(h != M1) *hval = h << 12; else return 0; 82 | h = hex_tab[*(++ptr)]; 83 | if(h != M1) *hval = *hval + (h << 8); else return 0; 84 | h = hex_tab[*(++ptr)]; 85 | if(h != M1) *hval = *hval + (h << 4); else return 0; 86 | h = hex_tab[*(++ptr)]; 87 | if(h != M1) *hval = *hval + h; else return 0; 88 | 89 | return 1; 90 | } 91 | 92 | #undef M1 93 | #undef MINUS1 94 | 95 | static inline unsigned char* 96 | ucs_to_utf8(unsigned char* ptr, unsigned ucs){ 97 | if(ucs < 0x80) { 98 | // 0yyyyyyy 99 | *(ptr++) = (unsigned char)ucs; 100 | return ptr; 101 | } else if(ucs < 0x800) { 102 | // 110xxxxy 10yyyyyy 103 | *(ptr++) = (unsigned char) 0xC0 + (ucs >> 6); 104 | *(ptr++) = (unsigned char) 0x80 + (ucs & 0x3F); 105 | return ptr; 106 | }else if(ucs < 0x1000) { 107 | // 1110xxxx 10xyyyyy 10yyyyyy 108 | if(ucs < 0xD800 || (ucs > 0xDFFF && ucs < 0xFFFE)) { 109 | *(ptr++) = (unsigned char) 0xE0 + (ucs >> 12); 110 | *(ptr++) = (unsigned char) 0x80 + ((ucs >> 6) & 0x3F); 111 | *(ptr++) = (unsigned char) 0x80 + (ucs & 0x3F); 112 | return ptr; 113 | } else { 114 | return NULL; 115 | } 116 | } else if(ucs < 0x10FFFF) { 117 | // 11110xxx 10xxyyyy 10yyyyyy 10yyyyyy 118 | *(ptr++) = (unsigned char) 0xF0 + (ucs >> 18); 119 | *(ptr++) = (unsigned char) 0x80 + ((ucs >> 12) & 0x3F); 120 | *(ptr++) = (unsigned char) 0x80 + ((ucs >> 6) & 0x3F); 121 | *(ptr++) = (unsigned char) 0x80 + (ucs & 0x3F); 122 | return ptr; 123 | } 124 | return NULL; 125 | } 126 | 127 | static inline int 128 | check_with_unescape_jstr(unsigned char *str, unsigned char **endstr, unsigned char **endptr){ 129 | unsigned char c, k; 130 | unsigned char *src = str; 131 | unsigned char *dst = str; 132 | for(;;){ 133 | c = *src; 134 | k = js_map[c]; 135 | if(!k){ *dst++ = *src++; continue;} 136 | if(c == '"'){*endstr = dst;*endptr = src; return 1;} 137 | switch(k){ 138 | case U2: 139 | *dst++ = *src++; 140 | if(js_map[*src] == R){ 141 | *dst++ = *src++; continue; 142 | }else{goto error;} 143 | case U3: 144 | *dst++ = *src++; 145 | if(js_map[*src] == R){ 146 | *dst++ = *src++; 147 | }else{goto error;} 148 | if(js_map[*src] == R){ 149 | *dst++ = *src++; continue; 150 | }else{goto error;} 151 | case U4: 152 | *dst++ = *src++; 153 | if(js_map[*src] == R){ 154 | *dst++ = *src++; 155 | }else{goto error;} 156 | if(js_map[*src] == R){ 157 | *dst++ = *src++; 158 | }else{goto error;} 159 | if(js_map[*src] == R){ 160 | *dst++ = *src++; continue; 161 | }else{goto error;} 162 | } 163 | if(c == '\\'){ 164 | src++; 165 | switch(*src){ 166 | case 'b' : {src++; *dst++ = 8U; continue;} 167 | case 't' : {src++; *dst++ = 9U; continue;} 168 | case 'n' : {src++; *dst++ = 10U; continue;} 169 | case 'v' : {src++; *dst++ = 11U; continue;} 170 | case 'f' : {src++; *dst++ = 12U; continue;} 171 | case 'r' : {src++; *dst++ = 13U; continue;} 172 | case '"' : {src++; *dst++ = 34U; continue;} 173 | case '/' : {src++; *dst++ = 47U; continue;} 174 | case '\\': {src++; *dst++ = 92U; continue;} 175 | case 'u': { 176 | unsigned hval; 177 | src++; 178 | if(!ucs_from_4hex(src, &hval)) {goto error;} 179 | if(!(dst = ucs_to_utf8(dst, hval))) {goto error;} 180 | src += 4; 181 | continue; 182 | } 183 | default: {goto error;} 184 | } 185 | } 186 | error: 187 | *endptr = NULL;*endstr = NULL; return 0; 188 | } 189 | } 190 | 191 | //Check json string(without escaped char) 192 | static inline int 193 | check_noescaped_jstr(unsigned char *str, unsigned char **endptr){ 194 | unsigned char c, k; 195 | size_t i = 0; 196 | for(;;){ 197 | c = str[++i]; 198 | k = js_map[c]; 199 | if(!k){ continue;} 200 | if(c == '"'){*endptr = str + i; return 1;} 201 | switch(k){ 202 | case U2: 203 | if(js_map[str[i + 1]] & R){ 204 | i++; continue; 205 | }else{goto error;} 206 | case U3: 207 | if(js_map[str[i + 1]] & js_map[str[i + 2]] & R){ 208 | i += 2; continue; 209 | }else{goto error;} 210 | case U4: 211 | if(js_map[str[i + 1]] & js_map[str[i + 2]] & js_map[str[i + 3]] & R){ 212 | i += 3; continue; 213 | }else{goto error;} 214 | } 215 | if(c == '\\'){*endptr = str + i; return 1;} 216 | error: 217 | *endptr = str + i; return 0; 218 | } 219 | } 220 | 221 | //Validate utf8 and calculate reserve bytes for escape chars 222 | static inline int 223 | check_str_for_json(unsigned char *str, unsigned len, unsigned *reserve){ 224 | unsigned char c; 225 | unsigned i; 226 | *reserve = 0; 227 | for(i=0; i> 4]; 293 | dst[di+4] = hexval[c & 0x0fU]; 294 | di += 5; 295 | continue; 296 | } 297 | } 298 | dst[di++] = src[si]; 299 | } 300 | return; 301 | } 302 | 303 | //Encode: escape string to json string 304 | static inline void 305 | extend_str_to_jstr(unsigned char *data, unsigned len, unsigned ext){ 306 | int di, si; 307 | unsigned char c, k; 308 | for(si = len - 1, di = len + ext - 1; si >= 0; si--){ 309 | c = data[si]; 310 | k = js_map[c]; 311 | if(k & E2E6){ 312 | // 313 | if(k == E2){ 314 | di -= 2; 315 | data[di+1] = '\\'; 316 | switch(c){ 317 | case 8: data[di+2] = 'b'; continue; 318 | case 9: data[di+2] = 't'; continue; 319 | case 10: data[di+2] = 'n'; continue; 320 | case 11: data[di+2] = 'v'; continue; 321 | case 12: data[di+2] = 'f'; continue; 322 | case 13: data[di+2] = 'r'; continue; 323 | case 34: data[di+2] = '"'; continue; 324 | case 92: data[di+2] = '\\'; continue; 325 | } 326 | }else{ 327 | data[di-5] = '\\'; 328 | data[di-4] = 'u'; 329 | data[di-3] = '0'; 330 | data[di-2] = '0'; 331 | data[di-1] = hexval[c >> 4]; 332 | data[di] = hexval[c & 0x0fU]; 333 | di -= 6; 334 | continue; 335 | } 336 | } 337 | data[di--] = data[si]; 338 | } 339 | return; 340 | }; 341 | -------------------------------------------------------------------------------- /c_src/encoder.c: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Yuriy Iskra 2 | 3 | #include 4 | #include 5 | #include 6 | #include "jsonx.h" 7 | #include "jsonx_str.h" 8 | 9 | #define FIRST_BIN_SZ (2 * 1024) 10 | 11 | typedef struct state_t{ 12 | PrivData* priv; 13 | ERL_NIF_TERM no_match; 14 | ERL_NIF_TERM ret; 15 | ErlNifBinary bin; 16 | unsigned char *cur; 17 | EncEntry *records; 18 | }State; 19 | 20 | static inline int match_atom(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 21 | static inline int match_binary(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 22 | static inline int match_int(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 23 | static inline int match_int64(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 24 | static inline int match_double(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 25 | static inline int match_string(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 26 | static inline int match_empty_list(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 27 | static inline int match_pair(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 28 | static inline int match_proplist(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 29 | static inline int match_list(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 30 | static inline int match_json(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 31 | static inline int match_tuple(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 32 | static inline int match_term(ErlNifEnv* env, ERL_NIF_TERM term, State *st); 33 | 34 | static void 35 | do_reserve(size_t sz, State *st){ 36 | size_t used = st->cur - st->bin.data; 37 | size_t t = st->bin.size * 2; 38 | while( t < used + sz ){ 39 | t *= 2; 40 | } 41 | enif_realloc_binary(&st->bin, t); 42 | st->cur = st->bin.data + used; 43 | } 44 | 45 | #define b_reserve(sz, st) if(sz > st->bin.data + st->bin.size - st->cur){do_reserve(sz, st);} 46 | #define b_putc(c, st) b_reserve(1, st);*(st->cur++) = c; 47 | #define b_putc2(c1, c2, st) b_reserve(2, st); st->cur[0]=c1; st->cur[1]=c2; st->cur+=2; 48 | #define b_unputc(st) st->cur--; 49 | #define b_seek(len, st) st->cur += (len); 50 | #define b_puts(len, str, st) b_reserve(len, st); memcpy(st->cur, str, len); b_seek(len, st); 51 | 52 | static inline int 53 | match_atom(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 54 | if(!enif_is_atom(env, term)){ 55 | return 0; 56 | } 57 | 58 | if(enif_is_identical(term, st->priv->am_true)){ 59 | b_puts(4, "true", st); 60 | return 1; 61 | }else if(enif_is_identical(term, st->priv->am_false)){ 62 | b_puts(5, "false", st); 63 | return 1; 64 | }else if(enif_is_identical(term, st->priv->am_null)){ 65 | b_puts(4, "null", st); 66 | return 1; 67 | } 68 | 69 | unsigned len, reserve; 70 | b_reserve(256 + 2, st); 71 | unsigned char *p = st->cur; 72 | if((len = enif_get_atom(env, term, (char*)p + 1, 256U, ERL_NIF_LATIN1))){ 73 | *p = '"'; 74 | if(!check_str_for_json((unsigned char*)p + 1, len-1, &reserve)){ 75 | return 0; 76 | } 77 | if(reserve > 0){ 78 | b_reserve(len + reserve + 2, st); 79 | extend_str_to_jstr((unsigned char*)p + 1, len-1, reserve); 80 | } 81 | st->cur += (len + reserve); 82 | b_putc('"', st); 83 | return 1; 84 | } 85 | return 0; 86 | } 87 | static inline int 88 | match_atom_as_string(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 89 | unsigned len, reserve; 90 | b_reserve(256 + 2, st); 91 | unsigned char *p = st->cur; 92 | if((len = enif_get_atom(env, term, (char*)p + 1, 256U, ERL_NIF_LATIN1))){ 93 | *p = '"'; 94 | if(!check_str_for_json((unsigned char*)p + 1, len-1, &reserve)){ 95 | return 0; 96 | } 97 | if(reserve > 0){ 98 | b_reserve(len + reserve + 2, st); 99 | extend_str_to_jstr((unsigned char*)p + 1, len-1, reserve); 100 | } 101 | st->cur += (len + reserve); 102 | b_putc('"', st); 103 | return 1; 104 | } 105 | return 0; 106 | } 107 | 108 | static inline int 109 | match_binary(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 110 | ErlNifBinary bin; 111 | unsigned reserve; 112 | if(!enif_inspect_binary(env, term, &bin)) 113 | return 0; 114 | if(!check_str_for_json(bin.data, bin.size, &reserve)){ 115 | //FIXME handle error 116 | return 0; 117 | } 118 | size_t len = bin.size + reserve; 119 | b_reserve(len + 2, st); 120 | *(st->cur++) = '"'; 121 | if(reserve > 0){ 122 | copy_str_to_jstr(st->cur, bin.data, bin.size); 123 | }else{ 124 | memcpy(st->cur, bin.data, len); 125 | } 126 | st->cur += len; 127 | *(st->cur++) = '"'; 128 | return 1; 129 | } 130 | 131 | static inline int 132 | match_int(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 133 | int ip, n; 134 | if(!enif_get_int(env, term, &ip)) 135 | return 0; 136 | b_reserve(24, st); 137 | n = sprintf((char*)(st->cur), "%d", ip); 138 | b_seek(n, st); 139 | return 1; 140 | } 141 | 142 | static inline int 143 | match_int64(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 144 | ErlNifSInt64 ip; 145 | int n; 146 | if(!enif_get_int64(env, term, &ip)) 147 | return 0; 148 | b_reserve(24, st); 149 | #if (defined(__WIN32__) || defined(_WIN32) || defined(_WIN32_)) 150 | n = sprintf((char*)st->cur, "%ld", ip); 151 | #elif SIZEOF_LONG == 8 152 | n = sprintf((char*)st->cur, "%ld", ip); 153 | #else 154 | n = sprintf((char*)st->cur, "%lld", ip); 155 | #endif 156 | b_seek(n, st); 157 | return 1; 158 | } 159 | 160 | static inline int 161 | match_double(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 162 | double dp; 163 | int n; 164 | if(!enif_get_double(env, term, &dp)) 165 | return 0; 166 | b_reserve(24, st); 167 | n = sprintf((char *)st->cur, "%.15g", dp); 168 | b_seek(n, st); 169 | return 1; 170 | } 171 | 172 | static inline int 173 | match_empty_list(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 174 | if(!enif_is_empty_list(env, term)){ 175 | return 0; 176 | } 177 | b_putc2('[', ']', st); 178 | return 1; 179 | } 180 | 181 | static inline int 182 | match_string(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 183 | if(match_binary(env, term, st)){ 184 | return 1; 185 | }else if(match_atom_as_string(env, term, st)){ 186 | return 1; 187 | } 188 | return 0; 189 | } 190 | 191 | static inline int 192 | match_pair(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 193 | const ERL_NIF_TERM *tuple; 194 | int arity; 195 | if(enif_get_tuple(env, term, &arity, &tuple)){ 196 | if(arity == 2){ 197 | if(match_string(env, tuple[0], st)){ 198 | b_putc(':', st); 199 | if(match_term(env, tuple[1], st)){ 200 | return 1; 201 | } 202 | } 203 | } 204 | } 205 | return 0; 206 | } 207 | 208 | static inline int 209 | match_proplist(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 210 | ERL_NIF_TERM list, head, tail; 211 | unsigned len, i; 212 | if(!enif_get_list_length(env, term, &len)){ 213 | return 0; 214 | } 215 | b_putc('{', st); 216 | list = term; 217 | for(i = 0; i < len; i++){ 218 | enif_get_list_cell(env, list, &head, &tail); 219 | if(i > 0){ 220 | b_putc(',', st); 221 | } 222 | if(!match_pair(env, head, st)){ 223 | return 0; 224 | } 225 | list = tail; 226 | } 227 | b_putc('}', st); 228 | return 1; 229 | } 230 | 231 | static inline int 232 | match_list(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 233 | ERL_NIF_TERM list, head, tail; 234 | unsigned len; 235 | if(!enif_get_list_length(env, term, &len)) 236 | return 0; 237 | b_putc('[', st); 238 | list = term; 239 | enif_get_list_cell(env, list, &head, &tail); 240 | if(!match_term(env, head, st)){ 241 | b_unputc(st); // delete '['; 242 | return 0; 243 | } 244 | list = tail; 245 | while(enif_get_list_cell(env, list, &head, &tail)){ 246 | b_putc(',', st); 247 | if(!match_term(env, head, st)){ 248 | return 0; 249 | } 250 | list = tail; 251 | } 252 | b_putc(']', st); 253 | return 1; 254 | } 255 | 256 | 257 | static inline int 258 | match_json(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 259 | ErlNifBinary bin; 260 | if(enif_inspect_iolist_as_binary(env, term, &bin)){ 261 | b_reserve(bin.size, st); 262 | memcpy(st->cur, bin.data, bin.size); 263 | b_seek(bin.size, st); 264 | return 1; 265 | } 266 | return 0; 267 | } 268 | 269 | static inline int 270 | enc_should_be_ignored(ErlNifEnv* env, ERL_NIF_TERM atom, const EncEntry* entry){ 271 | if(!enif_is_atom(env, atom)){ 272 | return 0; 273 | } 274 | unsigned i; 275 | for(i=0; iignored_len; ++i){ 276 | if(enif_is_identical(atom, entry->ignored[i])){ 277 | return 1; 278 | } 279 | } 280 | return 0; 281 | } 282 | 283 | 284 | static inline int 285 | match_record(ErlNifEnv* env, int arity, const ERL_NIF_TERM *tuple, State *st){ 286 | 287 | EncRecord *records = enc_records_base(st->records); 288 | EncField *fields = enc_fields_base(st->records); 289 | int i, k; 290 | for(i = 0; i < st->records->records_cnt; i++){ 291 | if(records[i].tag == tuple[0] && records[i].arity == (arity -1)){ 292 | unsigned fds_offset = records[i].fds_offset; 293 | unsigned bin_size = 0; 294 | b_putc('{', st); 295 | for(k = 0; k < records[i].arity; k++){ 296 | 297 | if(!enc_should_be_ignored(env, tuple[k+1], st->records)){ 298 | EncField field = fields[fds_offset + k]; 299 | bin_size += field.size; 300 | //FIXME { 301 | b_puts(field.size, st->records->bin.data + field.offset, st); 302 | if(!match_term(env, tuple[k+1], st)) 303 | return 0; 304 | } 305 | } 306 | b_putc('}', st); 307 | return 1; 308 | } 309 | } 310 | return 0; 311 | } 312 | 313 | static inline int 314 | match_tuple(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 315 | const ERL_NIF_TERM *tuple; 316 | int arity; 317 | if(!enif_get_tuple(env, term, &arity, &tuple)) 318 | return 0; 319 | if(arity > 0){ 320 | if(enif_is_atom(env, tuple[0])){ 321 | if(arity == 2){ 322 | if(enif_is_identical(tuple[0], st->priv->am_struct)){ 323 | //struct 324 | return match_proplist(env, tuple[1], st); 325 | }else if(enif_is_identical(tuple[0], st->priv->am_json)){ 326 | //json 327 | return match_json(env, tuple[1], st); 328 | } 329 | } 330 | //records 331 | if(st->records){ 332 | return match_record(env, arity, tuple, st); 333 | } 334 | } 335 | if(arity == 1){ 336 | //eep18 337 | return (match_proplist(env, tuple[0], st)); 338 | } 339 | } 340 | return 0; 341 | } 342 | 343 | static int 344 | match_term(ErlNifEnv* env, ERL_NIF_TERM term, State *st){ 345 | if (match_binary(env, term, st)){ 346 | return 1; 347 | }else if(match_atom(env, term, st)){ 348 | return 1; 349 | }else if(match_int(env, term, st)){ 350 | return 1; 351 | }else if(match_empty_list(env, term, st)){ 352 | return 1; 353 | }else if(match_list(env, term, st)){ 354 | return 1; 355 | }else if(match_proplist(env, term, st)){ 356 | return 1; 357 | }else if(match_tuple(env, term, st)){ 358 | return 1; 359 | }else if(match_double(env, term, st)){ 360 | return 1; 361 | }else if(match_int64(env, term, st)){ 362 | return 1; 363 | } 364 | if(!st->no_match){ 365 | st->no_match = term; 366 | } 367 | return 0; 368 | } 369 | 370 | ERL_NIF_TERM 371 | encode_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){ 372 | State st = { 373 | .priv = (PrivData*)enif_priv_data(env), 374 | .no_match = 0, 375 | .ret = 0, 376 | .bin = {0, NULL}, 377 | .cur = NULL, 378 | .records = NULL 379 | }; 380 | if(argc == 2){ 381 | EncEntry *entry_rs; 382 | assert(enif_get_resource(env, argv[1], st.priv->encoder_RSTYPE, (void**)&entry_rs)); 383 | st.records = entry_rs; 384 | } 385 | assert(enif_alloc_binary(FIRST_BIN_SZ, &st.bin)); 386 | st.cur = st.bin.data; 387 | 388 | if(match_term(env, argv[0], &st)){ 389 | enif_realloc_binary(&st.bin, st.cur - st.bin.data); 390 | return enif_make_binary(env, &st.bin); 391 | }else{ 392 | return enif_make_tuple2(env, st.priv->am_no_match, st.no_match); 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /c_src/jstream.c: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Yuriy Iskra 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "erl_nif.h" 8 | #include "jsonx_str.h" 9 | 10 | typedef struct{ 11 | ERL_NIF_TERM am_true; 12 | ERL_NIF_TERM am_false; 13 | ERL_NIF_TERM am_ok; 14 | 15 | ERL_NIF_TERM am_null; 16 | ERL_NIF_TERM am_start_map; 17 | ERL_NIF_TERM am_map_key; 18 | ERL_NIF_TERM am_end_map; 19 | ERL_NIF_TERM am_start_array; 20 | ERL_NIF_TERM am_end_array; 21 | ERL_NIF_TERM am_parse_buf; 22 | ERL_NIF_TERM am_parse_end; 23 | 24 | ERL_NIF_TERM am_error; 25 | ERL_NIF_TERM am_erange; 26 | ERL_NIF_TERM am_estr; 27 | ERL_NIF_TERM am_esyntax; 28 | }PrivData; 29 | 30 | #define ERROR 0 31 | #define START 1 32 | #define COMPLETTE 2 33 | #define COMMA 3 34 | #define KEY_COMPLETTE 4 35 | 36 | typedef struct{ 37 | ErlNifEnv *env; 38 | ErlNifBinary bin; 39 | unsigned char *cur; 40 | unsigned char *top; 41 | PrivData *priv; 42 | int m_state; 43 | }State; 44 | 45 | ErlNifResourceType* stream_RSTYPE; 46 | 47 | static void 48 | stream_rt_dtor(ErlNifEnv* env, void* obj){ 49 | State *entry = (void*)obj; 50 | enif_release_binary(&entry->bin); 51 | entry->bin.data = NULL; 52 | entry->bin.size = 0; 53 | } 54 | 55 | static int 56 | load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info){ 57 | 58 | PrivData *pdata = enif_alloc(sizeof(PrivData)); 59 | if(pdata == NULL) return 1; 60 | 61 | stream_RSTYPE = enif_open_resource_type(env, NULL, 62 | "stream_RSTYPE", 63 | stream_rt_dtor, 64 | ERL_NIF_RT_CREATE, NULL); 65 | if (stream_RSTYPE == NULL) return 1; 66 | 67 | if(!enif_make_existing_atom(env, "true", &(pdata->am_true), ERL_NIF_LATIN1)) return 1; 68 | if(!enif_make_existing_atom(env, "false", &(pdata->am_false), ERL_NIF_LATIN1)) return 1; 69 | if(!enif_make_existing_atom(env, "ok", &(pdata->am_ok), ERL_NIF_LATIN1)) return 1; 70 | 71 | if(!enif_make_existing_atom(env, "null", &(pdata->am_null), ERL_NIF_LATIN1)) return 1; 72 | if(!enif_make_existing_atom(env, "start_map", &(pdata->am_start_map), ERL_NIF_LATIN1)) return 1; 73 | if(!enif_make_existing_atom(env, "map_key", &(pdata->am_map_key), ERL_NIF_LATIN1)) return 1; 74 | if(!enif_make_existing_atom(env, "end_map", &(pdata->am_end_map), ERL_NIF_LATIN1)) return 1; 75 | if(!enif_make_existing_atom(env, "start_array", &(pdata->am_start_array), ERL_NIF_LATIN1)) return 1; 76 | if(!enif_make_existing_atom(env, "end_array", &(pdata->am_end_array), ERL_NIF_LATIN1)) return 1; 77 | if(!enif_make_existing_atom(env, "parse_buf", &(pdata->am_parse_buf), ERL_NIF_LATIN1)) return 1; 78 | if(!enif_make_existing_atom(env, "parse_end", &(pdata->am_parse_end), ERL_NIF_LATIN1)) return 1; 79 | 80 | if(!enif_make_existing_atom(env, "error", &(pdata->am_error), ERL_NIF_LATIN1)) return 1; 81 | if(!enif_make_existing_atom(env, "big_num", &(pdata->am_erange), ERL_NIF_LATIN1)) return 1; 82 | if(!enif_make_existing_atom(env, "invalid_string", &(pdata->am_estr), ERL_NIF_LATIN1)) return 1; 83 | if(!enif_make_existing_atom(env, "invalid_json", &(pdata->am_esyntax), ERL_NIF_LATIN1)) return 1; 84 | 85 | *priv_data = (void*)pdata; 86 | 87 | return 0; 88 | } 89 | 90 | static int 91 | reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info){ 92 | return 0; 93 | } 94 | 95 | static int 96 | upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info){ 97 | return 0; 98 | } 99 | static void 100 | unload(ErlNifEnv* env, void* priv_data){ 101 | return; 102 | } 103 | 104 | 105 | static inline unsigned char 106 | look_ah(State *st){ 107 | while(isspace(*st->cur)) 108 | st->cur++; 109 | return *(st->cur); 110 | } 111 | 112 | static inline ERL_NIF_TERM 113 | make_error(State* st, ERL_NIF_TERM resume){ 114 | st->m_state = ERROR; 115 | *st->top = 'e'; 116 | return enif_make_tuple2(st->env, st->priv->am_error, resume); 117 | } 118 | 119 | static inline ERL_NIF_TERM 120 | parse_string(State* st){ 121 | unsigned char *endptr; 122 | unsigned char *endstr; 123 | unsigned char *dst; 124 | ERL_NIF_TERM ret; 125 | if(check_noescaped_jstr(st->cur, &endptr)){ 126 | if(*endptr == '"'){ 127 | dst = enif_make_new_binary(st->env, endptr - st->cur - 1, &ret); 128 | memcpy(dst, st->cur + 1, endptr - st->cur - 1); 129 | st->cur = endptr + 1; 130 | return ret; 131 | }else if(*endptr == '\\'){ 132 | if(check_with_unescape_jstr(endptr, &endstr, &endptr)){ 133 | dst = enif_make_new_binary(st->env, endstr - st->cur - 1, &ret); 134 | memcpy(dst, st->cur + 1, endstr - st->cur - 1); 135 | st->cur = endptr + 1; 136 | return ret; 137 | } 138 | } 139 | } 140 | return make_error(st, st->priv->am_estr); 141 | } 142 | 143 | static inline ERL_NIF_TERM 144 | parse_number(State *st){ 145 | long long int_num; 146 | double float_num; 147 | char *endptr; 148 | errno = 0; 149 | 150 | int_num = strtoll((char *)st->cur, &endptr, 10); 151 | if((char*)st->cur == endptr){ 152 | return (ERL_NIF_TERM)0; 153 | }else if(errno == ERANGE){ 154 | return make_error(st, st->priv->am_erange); 155 | } 156 | 157 | if(*endptr == '.' || *endptr == 'e' || *endptr == 'E'){ 158 | float_num = strtod((char *)st->cur, &endptr); 159 | if(errno != ERANGE){ 160 | st->cur = (unsigned char*)endptr; 161 | return enif_make_double(st->env, float_num); 162 | } 163 | else{ 164 | return make_error(st, st->priv->am_erange); 165 | } 166 | } 167 | else{ 168 | st->cur = (unsigned char*)endptr; 169 | return enif_make_int64(st->env, int_num); 170 | } 171 | } 172 | 173 | static inline ERL_NIF_TERM 174 | parse_true(State* st){ 175 | if(!(strncmp("rue", (char*)(++st->cur), 3))){ 176 | st->cur = st->cur + 3; 177 | return st->priv->am_true; 178 | } 179 | return make_error(st, st->priv->am_esyntax); 180 | } 181 | 182 | static inline ERL_NIF_TERM 183 | parse_false(State* st){ 184 | if(!(strncmp("alse", (char*)(++st->cur), 4))){ 185 | st->cur = st->cur + 4; 186 | return st->priv->am_false; 187 | } 188 | return make_error(st, st->priv->am_esyntax); 189 | } 190 | 191 | static inline ERL_NIF_TERM 192 | parse_null(State* st){ 193 | if(!(strncmp("ull", (char*)(++st->cur), 3))){ 194 | st->cur = st->cur + 3; 195 | return st->priv->am_null; 196 | } 197 | return make_error(st, st->priv->am_esyntax); 198 | } 199 | 200 | 201 | static inline ERL_NIF_TERM 202 | parse_key(State *st){ 203 | ERL_NIF_TERM key; 204 | switch(look_ah(st)){ 205 | case '\"' : 206 | key = parse_string(st); 207 | if(st->m_state == ERROR) 208 | return key; 209 | if(look_ah(st) == ':'){ 210 | st->m_state = KEY_COMPLETTE; 211 | st->cur++; 212 | return enif_make_tuple2(st->env, st->priv->am_map_key, key); 213 | } 214 | case '\0' : 215 | return st->priv->am_parse_buf; 216 | default : 217 | return make_error(st, st->priv->am_esyntax); 218 | } 219 | } 220 | 221 | static inline ERL_NIF_TERM 222 | parse0(State *st){ 223 | switch(look_ah(st)){ 224 | case '\"' : st->m_state = COMPLETTE; return parse_string(st); 225 | case 't' : st->m_state = COMPLETTE; return parse_true(st); 226 | case 'f' : st->m_state = COMPLETTE; return parse_false(st); 227 | case 'n' : st->m_state = COMPLETTE; return parse_null(st); 228 | case '-' : case '0' : case '1' : case '2' : 229 | case '3' : case '4' : case '5' : case '6' : 230 | case '7' : case '8' : case '9' : 231 | st->m_state = COMPLETTE; return parse_number(st); 232 | case '[' : 233 | st->m_state = START; 234 | *(++st->top) = '['; st->cur++; 235 | return st->priv->am_start_array; 236 | case '{' : 237 | st->m_state = START; 238 | *(++st->top) = '{'; st->cur++; 239 | return st->priv->am_start_map; 240 | case '\0' : 241 | return st->priv->am_parse_buf; 242 | default: 243 | st->m_state = ERROR; 244 | *(st->top) = 'e'; 245 | return make_error(st, st->priv->am_esyntax); 246 | } 247 | } 248 | 249 | ERL_NIF_TERM 250 | get_event_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){ 251 | State *st; 252 | assert(enif_get_resource(env, argv[0], stream_RSTYPE, (void**)&st)); 253 | switch(*st->top){ 254 | case '[' : 255 | if(st->m_state == COMPLETTE){ 256 | switch(look_ah(st)){ 257 | case ',' : st->m_state = COMMA; st->cur++; return parse0(st); 258 | case '\0': st->cur++; return st->priv->am_parse_buf; 259 | case ']' : st->m_state = COMPLETTE; st->cur++; st->top--; return st->priv->am_end_array; 260 | } 261 | }else if(st->m_state == COMMA){ 262 | return parse0(st); 263 | }else if(st->m_state == START){ 264 | switch(look_ah(st)){ 265 | case '\0': st->cur++; return st->priv->am_parse_buf; 266 | case ']' : st->m_state = COMPLETTE; st->cur++; st->top--; return st->priv->am_end_array; 267 | default: return parse0(st); 268 | } 269 | } 270 | break; 271 | case '{' : 272 | if(st->m_state == COMPLETTE){ 273 | switch(look_ah(st)){ 274 | case ',' : st->m_state = COMMA; st->cur++; return parse_key(st); 275 | case '}' : st->m_state = COMPLETTE; st->cur++; st->top--; return st->priv->am_end_map; 276 | case '\0': st->cur++; return st->priv->am_parse_buf; 277 | } 278 | }else if(st->m_state == KEY_COMPLETTE){ 279 | return parse0(st); 280 | }else if(st->m_state == COMMA){ 281 | return parse_key(st); 282 | }else if(st->m_state == START){ 283 | switch(look_ah(st)){ 284 | case '\0': st->cur++; return st->priv->am_parse_buf; 285 | case '}' : st->m_state = COMPLETTE; st->cur++; st->top--; return st->priv->am_end_map; 286 | default: return parse_key(st); 287 | } 288 | } 289 | break; 290 | case '\0': 291 | if(st->m_state == COMPLETTE){ 292 | ERL_NIF_TERM rest; 293 | look_ah(st); 294 | size_t size = strlen((char*)st->cur); 295 | unsigned char *nb = enif_make_new_binary(env, size, &rest); 296 | memcpy(nb, st->cur, size); 297 | return enif_make_tuple2(env, st->priv->am_parse_end, rest); 298 | return st->priv->am_parse_end; 299 | }else{ 300 | return parse0(st); 301 | } 302 | break; 303 | case 'e': 304 | default : 305 | break; 306 | } 307 | return make_error(st, st->priv->am_esyntax); 308 | } 309 | 310 | ERL_NIF_TERM 311 | update_decoder_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){ 312 | State *st; 313 | ErlNifBinary input; 314 | if(!enif_inspect_binary(env, argv[1], &input)){ 315 | return enif_make_badarg(env); 316 | } 317 | assert(enif_get_resource(env, argv[0], stream_RSTYPE, (void**)&st)); 318 | size_t stack_size = (st->top + 1 - st->bin.data); 319 | 320 | size_t free = st->bin.size - stack_size - sizeof(ERL_NIF_TERM); 321 | if(input.size > free){ 322 | enif_realloc_binary(&st->bin, input.size + stack_size + sizeof(ERL_NIF_TERM)); 323 | st->top = st->bin.data + stack_size - 1; 324 | st->cur = st->top + 1; 325 | memcpy(st->cur, input.data, input.size); 326 | *((ERL_NIF_TERM*)(st->cur + input.size)) = 0U; 327 | }else{ 328 | st->cur = st->top + 1; 329 | memcpy(st->cur, input.data, input.size); 330 | *((ERL_NIF_TERM*)(st->cur + input.size)) = 0U; 331 | } 332 | return st->priv->am_ok; 333 | } 334 | 335 | ERL_NIF_TERM 336 | make_stream_resource_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){ 337 | ErlNifBinary input; 338 | if(!enif_inspect_binary(env, argv[0], &input)){ 339 | return enif_make_badarg(env); 340 | } 341 | size_t alloc_size = input.size + 2 * sizeof(ERL_NIF_TERM); 342 | State *st = (State*)enif_alloc_resource(stream_RSTYPE, sizeof(State)); 343 | st->env = env; 344 | st->priv = (PrivData*)enif_priv_data(env); 345 | st->m_state = START; 346 | 347 | enif_alloc_binary(alloc_size , &st->bin); 348 | st->cur = st->bin.data + sizeof(ERL_NIF_TERM); 349 | memcpy(st->cur, input.data, input.size); 350 | *((ERL_NIF_TERM*)st->bin.data) = 0U; 351 | *((ERL_NIF_TERM*)(st->bin.data + input.size + sizeof(ERL_NIF_TERM))) = 0U; 352 | st->top = st->cur - 1; 353 | 354 | ERL_NIF_TERM ret = enif_make_resource(env, (void *)st); 355 | enif_release_resource(st); 356 | return ret; 357 | } 358 | 359 | static ErlNifFunc 360 | nif_funcs[] = { 361 | {"new_decoder", 1, make_stream_resource_nif}, 362 | {"update_decoder", 2, update_decoder_nif}, 363 | {"get_event", 1, get_event_nif} 364 | }; 365 | 366 | ERL_NIF_INIT(jstream, nif_funcs, load, reload, upgrade, unload); 367 | 368 | -------------------------------------------------------------------------------- /c_src/decoder.c: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Yuriy Iskra 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "jsonx.h" 8 | #include "jsonx_str.h" 9 | 10 | #define JS_OFFSET (8 * sizeof(ERL_NIF_TERM)) 11 | 12 | typedef struct{ 13 | ErlNifEnv* env; 14 | PrivData *priv; 15 | size_t buf_size; 16 | unsigned char *buf; 17 | unsigned char *cur; 18 | size_t offset; 19 | ERL_NIF_TERM input; 20 | ERL_NIF_TERM format; //struct, eep18, proplist 21 | ERL_NIF_TERM error; 22 | ERL_NIF_TERM *stack_top; 23 | ERL_NIF_TERM *stack_down; 24 | DecEntry *resource; 25 | int strict_flag; 26 | } State; 27 | 28 | static inline ERL_NIF_TERM parse_json(State* st); 29 | static inline ERL_NIF_TERM parse_array(State* st); 30 | static inline ERL_NIF_TERM parse_object(State* st); 31 | static inline ERL_NIF_TERM parse_object_to_record(State* st); 32 | static inline ERL_NIF_TERM parse_string(State* st); 33 | static inline ERL_NIF_TERM parse_string_as_existing_atom(State* st); 34 | static inline ERL_NIF_TERM parse_number(State* st); 35 | static inline ERL_NIF_TERM parse_true(State* st); 36 | static inline ERL_NIF_TERM parse_false(State* st); 37 | static inline ERL_NIF_TERM parse_null(State* st); 38 | 39 | static inline void 40 | grow_stack(State *st){ 41 | size_t new_offset = 4 * st->offset; 42 | size_t new_size = st->buf_size - st->offset + new_offset; 43 | unsigned char *new_buf = enif_alloc(new_size); 44 | unsigned char *new_cur = (new_buf + new_size) - ((st->buf + st->buf_size) - st->cur); 45 | ERL_NIF_TERM *new_down = (ERL_NIF_TERM*)new_buf; 46 | ERL_NIF_TERM *new_top = new_down + (st->stack_top - st->stack_down); 47 | memcpy(new_cur, st->cur, (st->buf + st->buf_size) - st->cur); 48 | memcpy(new_down, st->stack_down, (void*)st->stack_top - (void*)st->stack_down); 49 | 50 | enif_free(st->buf); 51 | st->buf_size = new_size; 52 | st->offset = new_offset; 53 | st->cur = new_cur; 54 | st->buf = new_buf; 55 | st->stack_down = new_down; 56 | st->stack_top = new_top; 57 | } 58 | 59 | static inline unsigned char 60 | look_ah(State *st){ 61 | while(isspace(*st->cur)) 62 | st->cur++; 63 | return *(st->cur); 64 | } 65 | static inline void 66 | push_term(State *st, ERL_NIF_TERM val){ 67 | if(((unsigned char*)st->stack_top + sizeof(ERL_NIF_TERM)) > (st->cur)){ 68 | grow_stack(st); 69 | } 70 | *(st->stack_top++) = val; 71 | } 72 | 73 | static inline void 74 | reserve_stack(State* st, unsigned sz){ 75 | if(((unsigned char*)st->stack_top + sizeof(ERL_NIF_TERM) * sz) > (st->cur)){ 76 | grow_stack(st); 77 | } 78 | st->stack_top += sz; 79 | } 80 | 81 | static inline ERL_NIF_TERM 82 | parse_array(State* st){ 83 | ERL_NIF_TERM term; 84 | 85 | st->cur++; 86 | if(look_ah(st) == ']'){ 87 | st->cur++; 88 | return enif_make_list(st->env, 0); 89 | } 90 | 91 | size_t stack_off = st->stack_top - st->stack_down; 92 | for(;;){ 93 | if((term = parse_json(st))){ 94 | push_term(st, term); 95 | unsigned char c = look_ah(st); 96 | st->cur++; 97 | if(c == ','){ 98 | continue; 99 | }else if(c == ']'){ 100 | ERL_NIF_TERM *down = st->stack_down + stack_off; 101 | term = enif_make_list_from_array(st->env, down, st->stack_top - down); 102 | st->stack_top = down; 103 | return term; 104 | }else{ 105 | st->error = st->priv->am_esyntax; 106 | return (ERL_NIF_TERM)0; 107 | } 108 | }else{ 109 | return (ERL_NIF_TERM)0; 110 | } 111 | } 112 | return enif_make_atom(st->env, "array"); 113 | } 114 | 115 | static inline ERL_NIF_TERM 116 | parse_object(State* st){ 117 | ERL_NIF_TERM plist; 118 | ERL_NIF_TERM key, val, pair; 119 | unsigned char c; 120 | 121 | st->cur++; 122 | if(look_ah(st) == '}'){ 123 | st->cur++; 124 | plist = enif_make_list(st->env, 0); 125 | goto ret; 126 | } 127 | size_t stack_off = st->stack_top - st->stack_down; 128 | for(;;){ 129 | if(look_ah(st) == '"'){ 130 | if((key = parse_string(st))){ 131 | if(look_ah(st) == ':'){ 132 | st->cur++; 133 | if((val = parse_json(st))){ 134 | pair = enif_make_tuple2(st->env, key, val); 135 | push_term(st, pair); 136 | c = look_ah(st); 137 | st->cur++; 138 | if(c == ','){ 139 | continue; 140 | }else if(c == '}'){ 141 | ERL_NIF_TERM *down = st->stack_down + stack_off; 142 | plist = enif_make_list_from_array(st->env, down, st->stack_top - down); 143 | st->stack_top = down; 144 | goto ret; 145 | } 146 | } 147 | } 148 | } 149 | } 150 | if(!st->error){ 151 | st->error = st->priv->am_esyntax; 152 | } 153 | return (ERL_NIF_TERM)0; 154 | } 155 | ret: 156 | if(st->format == st->priv->am_struct){ 157 | return enif_make_tuple2(st->env, st->priv->am_struct, plist); 158 | }else if(st->format == st->priv->am_eep18){ 159 | return enif_make_tuple1(st->env, plist); 160 | }else if(st->format == st->priv->am_proplist){ 161 | return plist; 162 | } 163 | assert(0); 164 | } 165 | 166 | static inline ERL_NIF_TERM 167 | parse_object_to_record(State* st){ 168 | ERL_NIF_TERM record; 169 | ERL_NIF_TERM key, val; 170 | ERL_NIF_TERM *pairs; 171 | unsigned char c; 172 | unsigned arity; 173 | int record_num,i,k; 174 | size_t results_off; 175 | size_t stack_off = st->stack_top - st->stack_down; 176 | size_t cur_off = st->cur - st->buf; 177 | ERL_NIF_TERM* ukeys = ukeys_base(st->resource); 178 | unsigned* keys = keys_base(st->resource, st->resource->ukeys_cnt); 179 | DecRecord* records = records_base(st->resource, st->resource->ukeys_cnt, st->resource->keys_cnt); 180 | long *masks_base = bit_mask_base(st->resource, st->resource->ukeys_cnt, st->resource->keys_cnt, st->resource->records_cnt); 181 | size_t mask_size = BITS_TO_ETERM(st->resource->ukeys_cnt); 182 | size_t mask_off = stack_off; 183 | size_t pairs_off = stack_off + mask_size; 184 | unsigned pair_cnt = 0; 185 | reserve_stack(st, mask_size); // for mask 186 | for(i = 0; i < mask_size; i++){ 187 | (st->stack_down + mask_off)[i] = 0; 188 | } 189 | st->cur++; 190 | if(look_ah(st) == '}'){ 191 | st->cur++; 192 | goto ret; 193 | } 194 | for(;;){ 195 | if(look_ah(st) == '"'){ 196 | if((key = parse_string_as_existing_atom(st))){ 197 | //FIXME search in sorted array 198 | int key_num = -1; 199 | for(i = 0; i < st->resource->ukeys_cnt; i++){ 200 | if(enif_is_identical(ukeys[i], key)){ 201 | key_num = i; 202 | break; 203 | } 204 | } 205 | if(key_num == -1) 206 | goto undefrec; 207 | set_bit(key_num, (long*)st->stack_down + mask_off); 208 | push_term(st, key); 209 | if(look_ah(st) == ':'){ 210 | st->cur++; 211 | if((val = parse_json(st))){ 212 | push_term(st, val); 213 | pair_cnt++; 214 | c = look_ah(st); 215 | st->cur++; 216 | if(c == ','){ 217 | continue; 218 | }else if(c == '}'){ 219 | goto ret; 220 | } 221 | } 222 | } 223 | }else{ 224 | if(st->error){ 225 | return (ERL_NIF_TERM)0; 226 | }else{ 227 | goto undefrec; 228 | } 229 | } 230 | } 231 | if(!st->error) 232 | st->error = st->priv->am_esyntax; 233 | return (ERL_NIF_TERM)0; 234 | } 235 | ret: 236 | record_num = find_mask(st->resource->ukeys_cnt, (long *)(st->stack_down + mask_off), 237 | st->resource->records_cnt, masks_base); 238 | if(record_num < 0) 239 | goto undefrec; 240 | results_off = st->stack_top - st->stack_down; 241 | push_term(st, records[record_num].tag); 242 | unsigned pos = records[record_num].keys_off; 243 | arity = records[record_num].arity; 244 | if(arity != pair_cnt) 245 | goto undefrec; 246 | for(i = 0; i < arity; i++){ 247 | unsigned knum = keys[pos + i]; 248 | for(k = 0; k < arity; k++){ 249 | pairs = st->stack_down + pairs_off; 250 | if(enif_is_identical(pairs[2 * k], ukeys[knum])){ 251 | push_term(st, pairs[2 * k + 1]); 252 | } 253 | } 254 | } 255 | record = enif_make_tuple_from_array(st->env, st->stack_down + results_off, arity + 1); 256 | st->stack_top = st->stack_down + stack_off; 257 | return record; 258 | undefrec: 259 | if(st->strict_flag){ 260 | st->error = st->priv->am_undefined_record; 261 | return (ERL_NIF_TERM)0; 262 | }else{ 263 | st->cur = st->buf + cur_off; 264 | st->stack_top = st->stack_down + stack_off; 265 | return parse_object(st); 266 | } 267 | } 268 | 269 | static inline ERL_NIF_TERM 270 | parse_string(State* st){ 271 | unsigned char *endptr; 272 | unsigned char *endstr; 273 | ERL_NIF_TERM ret; 274 | if(check_noescaped_jstr(st->cur, &endptr)){ 275 | if(*endptr == '"'){ 276 | ret = enif_make_sub_binary(st->env, st->input, st->cur - (st->buf + st->offset) + 1, endptr - st->cur - 1); 277 | st->cur = endptr + 1; 278 | return ret; 279 | }else if(*endptr == '\\'){ 280 | if(check_with_unescape_jstr(endptr, &endstr, &endptr)){ 281 | unsigned char *dst = enif_make_new_binary(st->env, endstr - st->cur - 1, &ret); 282 | memcpy(dst, st->cur + 1, endstr - st->cur - 1); 283 | st->cur = endptr + 1; 284 | return ret; 285 | } 286 | } 287 | } 288 | st->error = st->priv->am_estr; 289 | return (ERL_NIF_TERM)0; 290 | } 291 | static inline ERL_NIF_TERM 292 | parse_string_as_existing_atom(State* st){ 293 | unsigned char *endptr; 294 | unsigned char *endstr; 295 | ERL_NIF_TERM atom; 296 | if(check_noescaped_jstr(st->cur, &endptr)){ 297 | if(*endptr == '"'){ 298 | if(!enif_make_existing_atom_len(st->env, (const char*)(st->cur + 1), endptr - st->cur - 1, &atom, ERL_NIF_LATIN1)){ 299 | return (ERL_NIF_TERM)0; 300 | } 301 | st->cur = endptr + 1; 302 | return atom; 303 | }else if(*endptr == '\\'){ 304 | if(check_with_unescape_jstr(endptr, &endstr, &endptr)){ 305 | if(!enif_make_existing_atom_len(st->env, (const char*)(st->cur + 1), endstr - st->cur - 1, &atom, ERL_NIF_LATIN1)){ 306 | return (ERL_NIF_TERM)0; 307 | } 308 | st->cur = endptr + 1; 309 | return atom; 310 | } 311 | } 312 | } 313 | st->error = st->priv->am_estr; 314 | return (ERL_NIF_TERM)0; 315 | } 316 | 317 | static inline ERL_NIF_TERM 318 | parse_number(State *st){ 319 | long long int_num; 320 | double float_num; 321 | char *endptr; 322 | errno = 0; 323 | 324 | int_num = strtoll((char *)st->cur, &endptr, 10); 325 | if((char*)st->cur == endptr){ 326 | return (ERL_NIF_TERM)0; 327 | }else if(errno == ERANGE){ 328 | st->error = st->priv->am_erange; 329 | return (ERL_NIF_TERM)0; 330 | } 331 | 332 | if(*endptr == '.' || *endptr == 'e' || *endptr == 'E'){ 333 | float_num = strtod((char *)st->cur, &endptr); 334 | if(errno != ERANGE){ 335 | st->cur = (unsigned char*)endptr; 336 | return enif_make_double(st->env, float_num); 337 | } 338 | else{ 339 | st->error = st->priv->am_erange; 340 | return (ERL_NIF_TERM)0; 341 | } 342 | } 343 | else{ 344 | st->cur = (unsigned char*)endptr; 345 | return enif_make_int64(st->env, int_num); 346 | } 347 | } 348 | 349 | static inline ERL_NIF_TERM 350 | parse_true(State* st){ 351 | if(!(strncmp("rue", (char*)(++st->cur), 3))){ 352 | st->cur = st->cur + 3; 353 | return st->priv->am_true; 354 | } 355 | st->error = st->priv->am_esyntax; 356 | return (ERL_NIF_TERM)0; 357 | } 358 | 359 | static inline ERL_NIF_TERM 360 | parse_false(State* st){ 361 | if(!(strncmp("alse", (char*)(++st->cur), 4))){ 362 | st->cur = st->cur + 4; 363 | return st->priv->am_false; 364 | } 365 | st->error = st->priv->am_esyntax; 366 | return (ERL_NIF_TERM)0; 367 | } 368 | 369 | static inline ERL_NIF_TERM 370 | parse_null(State* st){ 371 | if(!(strncmp("ull", (char*)(++st->cur), 3))){ 372 | st->cur = st->cur + 3; 373 | return st->priv->am_null; 374 | } 375 | st->error = st->priv->am_esyntax; 376 | return (ERL_NIF_TERM)0; 377 | } 378 | 379 | static inline ERL_NIF_TERM 380 | parse_json(State *st){ 381 | ERL_NIF_TERM num; 382 | switch(look_ah(st)){ 383 | case '\"' : return parse_string(st); 384 | case '{' : return (st->resource ? parse_object_to_record(st) : parse_object(st)); 385 | case '[' : return parse_array(st); 386 | case 't' : return parse_true(st); 387 | case 'f' : return parse_false(st); 388 | case 'n' : return parse_null(st); 389 | default: 390 | if((num = parse_number(st))){ 391 | return num; 392 | } 393 | if(!st->error){ 394 | st->error = st->priv->am_esyntax; 395 | } 396 | return (ERL_NIF_TERM)0; 397 | } 398 | } 399 | 400 | ERL_NIF_TERM 401 | decode_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]){ 402 | ErlNifBinary input; 403 | if(!enif_inspect_binary(env, argv[0], &input)){ 404 | return enif_make_badarg(env); 405 | } 406 | assert(argc == 2 || argc == 4 ); 407 | State st; 408 | st.priv = (PrivData*)enif_priv_data(env); 409 | st.resource = NULL; 410 | st.input = argv[0]; 411 | st.format = argv[1]; 412 | if (argc == 4){ // whith resource 413 | assert(enif_get_resource(env, argv[2], st.priv->decoder_RSTYPE, (void**)&st.resource)); 414 | st.strict_flag = enif_is_identical(st.priv->am_true, argv[3]) ? 1 : 0; 415 | } 416 | st.offset = JS_OFFSET; 417 | st.buf_size = st.offset + input.size + 4; 418 | st.buf = enif_alloc(st.buf_size); 419 | st.env = env; 420 | st.stack_top = st.stack_down = (ERL_NIF_TERM*)st.buf; 421 | st.cur = st.buf + st.offset; 422 | st.error = (ERL_NIF_TERM)0; 423 | memcpy(st.cur, input.data, input.size); 424 | st.buf[st.buf_size - 1] = 0U; 425 | st.buf[st.buf_size - 2] = 0U; 426 | st.buf[st.buf_size - 3] = 0U; 427 | st.buf[st.buf_size - 4] = 0U; 428 | 429 | ERL_NIF_TERM ret = parse_json(&st); 430 | if(ret){ 431 | if(look_ah(&st) != 0U){ 432 | ret = 0; 433 | st.error = st.priv->am_etrailing; 434 | } 435 | } 436 | enif_free(st.buf); 437 | if(!ret){ 438 | return enif_make_tuple3(env, st.priv->am_error, st.error, 439 | enif_make_ulong(env, st.cur - (st.buf + st.offset))); 440 | } 441 | return ret; 442 | } 443 | --------------------------------------------------------------------------------