Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ see below | `datetime()`
* arrays

json arrays are represented with erlang lists of json values as described
in this section
in this section, or by tuples with the `tuples_to_lists` option

* objects

Expand Down Expand Up @@ -272,7 +272,8 @@ json_term() = [json_term()]
| float()
| binary()
| atom()
| datetime()
| datetime()
| tuple()
```

the erlang representation of json. binaries should be `utf8` encoded, or close
Expand Down Expand Up @@ -318,6 +319,7 @@ option() = dirty_strings
| return_tail
| uescape
| unescaped_jsonp
| tuples_to_lists

strict_option() = comments
| trailing_commas
Expand Down Expand Up @@ -495,7 +497,7 @@ encode(Term, Opts) -> JSON

Term = json_term()
JSON = json_text()
Opts = [option() | space | {space, N} | indent | {indent, N}]
Opts = [option() | space | {space, N} | indent | {indent, N} | tuples_to_lists]
N = pos_integer()
```

Expand Down
24 changes: 20 additions & 4 deletions src/jsx_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
| {indent, non_neg_integer()}
| {depth, non_neg_integer()}
| {newline, binary()}
| {tuples_to_lists, boolean()}
| {disable_timestamp_euristics, boolean()}
Comment thread
brigadier marked this conversation as resolved.
Outdated
| legacy_option()
| {legacy_option(), boolean()}.
-type legacy_option() :: strict_comments
Expand Down Expand Up @@ -114,6 +116,10 @@ parse_config([multi_term|Rest], Config) ->
parse_config(Rest, Config#config{multi_term=true});
parse_config([return_tail|Rest], Config) ->
parse_config(Rest, Config#config{return_tail=true});
parse_config([tuples_to_lists|Rest], Config) ->
parse_config(Rest, Config#config{tuples_to_lists = true});
parse_config([disable_timestamp_euristics|Rest], Config) ->
parse_config(Rest, Config#config{disable_timestamp_euristics = true});
%% retained for backwards compat, now does nothing however
parse_config([repeat_keys|Rest], Config) ->
parse_config(Rest, Config);
Expand Down Expand Up @@ -215,7 +221,9 @@ valid_flags() ->
stream,
uescape,
error_handler,
incomplete_handler
incomplete_handler,
tuples_to_lists,
disable_timestamp_euristics
].


Expand Down Expand Up @@ -259,7 +267,9 @@ config_test_() ->
strict_escapes = true,
strict_control_codes = true,
stream = true,
uescape = true
uescape = true,
tuples_to_lists = true,
disable_timestamp_euristics = true
},
parse_config([dirty_strings,
escaped_forward_slashes,
Expand All @@ -270,7 +280,9 @@ config_test_() ->
repeat_keys,
strict,
stream,
uescape
uescape,
tuples_to_lists,
disable_timestamp_euristics
])
)
},
Expand Down Expand Up @@ -342,6 +354,8 @@ config_to_list_test_() ->
stream,
uescape,
unescaped_jsonp,
tuples_to_lists,
disable_timestamp_euristics,
strict
],
config_to_list(
Expand All @@ -356,7 +370,9 @@ config_to_list_test_() ->
strict_escapes = true,
strict_control_codes = true,
stream = true,
uescape = true
uescape = true,
tuples_to_lists = true,
disable_timestamp_euristics = true
}
)
)},
Expand Down
4 changes: 3 additions & 1 deletion src/jsx_config.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@
uescape = false :: boolean(),
unescaped_jsonp = false :: boolean(),
error_handler = false :: false | jsx_config:handler(),
incomplete_handler = false :: false | jsx_config:handler()
incomplete_handler = false :: false | jsx_config:handler(),
tuples_to_lists = false :: boolean(),
disable_timestamp_euristics = false :: boolean()
}).
72 changes: 40 additions & 32 deletions src/jsx_encoder.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,58 +22,66 @@


-module(jsx_encoder).

-export([encoder/3, encode/1, encode/2]).
-include("jsx_config.hrl").
-export([encoder/3, encode/2, encode/3]).

-spec encoder(Handler::module(), State::any(), Config::jsx_config:options()) -> jsx:encoder().

encoder(Handler, State, Config) ->
Parser = jsx:parser(Handler, State, Config),
fun(Term) -> Parser(encode(Term) ++ [end_json]) end.
fun(Term) -> Parser(encode(Term, jsx_config:parse_config(Config)) ++ [end_json]) end.


-spec encode(Term::any()) -> [any(), ...].
-spec encode(Term::any(), Config::jsx_config:options()) -> [any(), ...].

encode(Term) -> encode(Term, ?MODULE).
encode(Term, Config) -> encode(Term, ?MODULE, Config).


-spec encode(Term::any(), EntryPoint::module()) -> [any(), ...].
-spec encode(Term::any(), EntryPoint::module(), Config::jsx_config:options()) -> [any(), ...].

encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 ->
encode(Map, _EntryPoint, _Config) when is_map(Map), map_size(Map) < 1 ->
[start_object, end_object];
encode(Term, EntryPoint) when is_map(Term) ->
[start_object] ++ unpack(Term, EntryPoint);
encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
encode(Term, EntryPoint, Config) when is_map(Term) ->
[start_object] ++ unpack(Term, EntryPoint, Config);
encode(Term, EntryPoint, Config) -> encode_(Term, EntryPoint, Config).

encode_([], _EntryPoint) -> [start_array, end_array];
encode_([{}], _EntryPoint) -> [start_object, end_object];
encode_([], _EntryPoint, _Config) -> [start_array, end_array];
encode_([{}], _EntryPoint,#config{tuples_to_lists = false}) -> [start_object, end_object];

%% datetime special case
encode_([{{_,_,_},{_,_,_}} = DateTime|Rest], EntryPoint) ->
[start_array] ++ [DateTime] ++ unhitch(Rest, EntryPoint);
encode_([{_, _}|_] = Term, EntryPoint) ->
[start_object] ++ unzip(Term, EntryPoint);
encode_(Term, EntryPoint) when is_list(Term) ->
[start_array] ++ unhitch(Term, EntryPoint);
encode_([{{_,_,_},{_,_,_}} = DateTime|Rest], EntryPoint, #config{tuples_to_lists = false} = Config) ->
[start_array] ++ [DateTime] ++ unhitch(Rest, EntryPoint, Config);
encode_({{A,B,C},{D,E,F}} = DateTime, _EntryPoint, #config{tuples_to_lists = true, disable_timestamp_euristics = false} = _Config)
when is_integer(A), is_integer(B), is_integer(C), is_integer(D), is_integer(E), is_integer(F) ->
[DateTime];
encode_({A,B,C} = Timestamp, _EntryPoint, #config{tuples_to_lists = true, disable_timestamp_euristics = false} = _Config)
when is_integer(A), is_integer(B), is_integer(C) ->
[Timestamp];

encode_(Else, _EntryPoint) -> [Else].
encode_([{_, _}|_] = Term, EntryPoint, #config{tuples_to_lists = false} = Config) ->
[start_object] ++ unzip(Term, EntryPoint, Config);
encode_(Term, EntryPoint, Config) when is_list(Term) ->
[start_array] ++ unhitch(Term, EntryPoint, Config);
encode_(T, EntryPoint, #config{tuples_to_lists = true} = Config) when is_tuple(T)->
encode_(tuple_to_list(T), EntryPoint, Config);
encode_(Else, _EntryPoint, _Config) -> [Else].


unzip([{K, V}|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
[K] ++ EntryPoint:encode(V, EntryPoint) ++ unzip(Rest, EntryPoint);
unzip([], _) -> [end_object];
unzip(_, _) -> erlang:error(badarg).
unzip([{K, V}|Rest], EntryPoint, Config) when is_integer(K); is_binary(K); is_atom(K) ->
[K] ++ EntryPoint:encode(V, EntryPoint, Config) ++ unzip(Rest, EntryPoint, Config);
unzip([], _, _) -> [end_object];
unzip(_, _, _) -> erlang:error(badarg).


unhitch([V|Rest], EntryPoint) ->
EntryPoint:encode(V, EntryPoint) ++ unhitch(Rest, EntryPoint);
unhitch([], _) -> [end_array].
unhitch([V|Rest], EntryPoint, Config) ->
EntryPoint:encode(V, EntryPoint, Config) ++ unhitch(Rest, EntryPoint, Config);
unhitch([], _, _) -> [end_array].

unpack(Map, EntryPoint) -> unpack(Map, maps:keys(Map), EntryPoint).
unpack(Map, EntryPoint, Config) -> unpack(Map, maps:keys(Map), EntryPoint, Config).

unpack(Map, [K|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
[K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint) ++ unpack(Map, Rest, EntryPoint);
unpack(_, [], _) -> [end_object].
unpack(Map, [K|Rest], EntryPoint, Config) when is_integer(K); is_binary(K); is_atom(K) ->
[K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint, Config) ++ unpack(Map, Rest, EntryPoint, Config);
unpack(_, [], _, _) -> [end_object].

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
Expand Down Expand Up @@ -105,11 +113,11 @@ improper_lists_test_() ->
[
{"improper proplist", ?_assertError(
badarg,
encode([{<<"key">>, <<"value">>}, false])
encode([{<<"key">>, <<"value">>}, false], #config{})
)},
{"improper list", ?_assertError(
badarg,
encode([{literal, true}, false, null])
encode([{literal, true}, false, null], #config{})
)}
].

Expand Down
6 changes: 5 additions & 1 deletion src/jsx_to_json.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
space = 0,
indent = 0,
depth = 0,
newline = <<$\n>>
newline = <<$\n>>,
tuples_to_lists = false,
disable_timestamp_euristics = false
}).

-type config() :: proplists:proplist().
Expand Down Expand Up @@ -62,6 +64,8 @@ parse_config([{indent, Val}|Rest], Config) when is_integer(Val), Val > 0 ->
parse_config(Rest, Config#config{indent = Val});
parse_config([indent|Rest], Config) ->
parse_config(Rest, Config#config{indent = 1});
parse_config([tuples_to_lists|Rest], Config) ->
parse_config(Rest, Config#config{tuples_to_lists = true});
parse_config([{newline, Val}|Rest], Config) when is_binary(Val) ->
parse_config(Rest, Config#config{newline = Val});
parse_config([{K, _}|Rest] = Options, Config) ->
Expand Down