- % This file is part of Jiffy released under the MIT license.
- % See the LICENSE file for more information.
-
- -module(jiffy).
- -export([decode/1, decode/2, encode/1, encode/2]).
- -define(NOT_LOADED, not_loaded(?LINE)).
-
- -compile([no_native]).
-
- -on_load(init/0).
-
-
- -type json_value() :: null
- | true
- | false
- | json_string()
- | json_number()
- | json_object()
- | json_array().
-
- -type json_array() :: [json_value()].
- -type json_string() :: atom() | binary().
- -type json_number() :: integer() | float().
-
- -ifdef(JIFFY_NO_MAPS).
-
- -type json_object() :: {[{json_string(),json_value()}]}.
-
- -else.
-
- -type json_object() :: {[{json_string(),json_value()}]}
- | #{json_string() => json_value()}.
-
- -endif.
-
- -type jiffy_decode_result() :: json_value()
- | {has_trailer, json_value(), binary()}.
-
- -type decode_option() :: return_maps
- | use_nil
- | return_trailer
- | {null_term, any()}
- | {bytes_per_iter, non_neg_integer()}
- | {bytes_per_red, non_neg_integer()}.
-
- -type encode_option() :: uescape
- | pretty
- | force_utf8
- | escape_forward_slashes
- | {bytes_per_iter, non_neg_integer()}
- | {bytes_per_red, non_neg_integer()}.
-
- -type decode_options() :: [decode_option()].
- -type encode_options() :: [encode_option()].
-
- -export_type([json_value/0, jiffy_decode_result/0]).
-
-
- -spec decode(iolist() | binary()) -> jiffy_decode_result().
- decode(Data) ->
- decode(Data, []).
-
-
- -spec decode(iolist() | binary(), decode_options()) -> json_value().
- decode(Data, Opts) when is_binary(Data), is_list(Opts) ->
- case nif_decode_init(Data, Opts) of
- {error, _} = Error ->
- throw(Error);
- {partial, EJson} ->
- finish_decode(EJson);
- {iter, Decoder, Val, Objs, Curr} ->
- decode_loop(Data, Decoder, Val, Objs, Curr);
- EJson ->
- EJson
- end;
- decode(Data, Opts) when is_list(Data) ->
- decode(iolist_to_binary(Data), Opts).
-
-
- -spec encode(json_value()) -> iolist().
- encode(Data) ->
- encode(Data, []).
-
-
- -spec encode(json_value(), encode_options()) -> iolist().
- encode(Data, Options) ->
- ForceUTF8 = lists:member(force_utf8, Options),
- case nif_encode_init(Data, Options) of
- {error, {invalid_string, _}} when ForceUTF8 == true ->
- FixedData = jiffy_utf8:fix(Data),
- encode(FixedData, Options -- [force_utf8]);
- {error, _} = Error ->
- throw(Error);
- {partial, IOData} ->
- finish_encode(IOData, []);
- {iter, Encoder, Stack, IOBuf} ->
- encode_loop(Data, Options, Encoder, Stack, IOBuf);
- IOData ->
- IOData
- end.
-
-
- finish_decode({bignum, Value}) ->
- list_to_integer(binary_to_list(Value));
- finish_decode({bignum_e, Value}) ->
- {IVal, EVal} = case string:to_integer(binary_to_list(Value)) of
- {I, [$e | ExpStr]} ->
- {E, []} = string:to_integer(ExpStr),
- {I, E};
- {I, [$E | ExpStr]} ->
- {E, []} = string:to_integer(ExpStr),
- {I, E}
- end,
- IVal * math:pow(10, EVal);
- finish_decode({bigdbl, Value}) ->
- list_to_float(binary_to_list(Value));
- finish_decode({Pairs}) when is_list(Pairs) ->
- finish_decode_obj(Pairs, []);
- finish_decode(Vals) when is_list(Vals) ->
- finish_decode_arr(Vals, []);
- finish_decode(Val) ->
- maybe_map(Val).
-
- -ifndef(JIFFY_NO_MAPS).
- maybe_map(Obj) when is_map(Obj) ->
- maps:map(fun finish_decode_map/2, Obj);
- maybe_map(Val) ->
- Val.
-
- finish_decode_map(_, V) ->
- finish_decode(V).
- -else.
- maybe_map(Val) ->
- Val.
- -endif.
-
- finish_decode_obj([], Acc) ->
- {lists:reverse(Acc)};
- finish_decode_obj([{K, V} | Pairs], Acc) ->
- finish_decode_obj(Pairs, [{K, finish_decode(V)} | Acc]).
-
- finish_decode_arr([], Acc) ->
- lists:reverse(Acc);
- finish_decode_arr([V | Vals], Acc) ->
- finish_decode_arr(Vals, [finish_decode(V) | Acc]).
-
-
- finish_encode([], Acc) ->
- %% No reverse! The NIF returned us
- %% the pieces in reverse order.
- Acc;
- finish_encode([<<_/binary>>=B | Rest], Acc) ->
- finish_encode(Rest, [B | Acc]);
- finish_encode([Val | Rest], Acc) when is_integer(Val) ->
- Bin = list_to_binary(integer_to_list(Val)),
- finish_encode(Rest, [Bin | Acc]);
- finish_encode([InvalidEjson | _], _) ->
- throw({error, {invalid_ejson, InvalidEjson}});
- finish_encode(_, _) ->
- throw({error, invalid_ejson}).
-
-
- init() ->
- PrivDir = case code:priv_dir(?MODULE) of
- {error, _} ->
- EbinDir = filename:dirname(code:which(?MODULE)),
- AppPath = filename:dirname(EbinDir),
- filename:join(AppPath, "priv");
- Path ->
- Path
- end,
- erlang:load_nif(filename:join(PrivDir, "jiffy"), 0).
-
-
- decode_loop(Data, Decoder, Val, Objs, Curr) ->
- case nif_decode_iter(Data, Decoder, Val, Objs, Curr) of
- {error, _} = Error ->
- throw(Error);
- {partial, EJson} ->
- finish_decode(EJson);
- {iter, NewDecoder, NewVal, NewObjs, NewCurr} ->
- decode_loop(Data, NewDecoder, NewVal, NewObjs, NewCurr);
- EJson ->
- EJson
- end.
-
-
- encode_loop(Data, Options, Encoder, Stack, IOBuf) ->
- ForceUTF8 = lists:member(force_utf8, Options),
- case nif_encode_iter(Encoder, Stack, IOBuf) of
- {error, {invalid_string, _}} when ForceUTF8 == true ->
- FixedData = jiffy_utf8:fix(Data),
- encode(FixedData, Options -- [force_utf8]);
- {error, _} = Error ->
- throw(Error);
- {partial, IOData} ->
- finish_encode(IOData, []);
- {iter, NewEncoder, NewStack, NewIOBuf} ->
- encode_loop(Data, Options, NewEncoder, NewStack, NewIOBuf);
- IOData ->
- IOData
- end.
-
-
- not_loaded(Line) ->
- erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).
-
- nif_decode_init(_Data, _Opts) ->
- ?NOT_LOADED.
-
- nif_decode_iter(_Data, _Decoder, _, _, _) ->
- ?NOT_LOADED.
-
- nif_encode_init(_Data, _Options) ->
- ?NOT_LOADED.
-
- nif_encode_iter(_Encoder, _Stack, _IoList) ->
- ?NOT_LOADED.
|