Created
August 2, 2010 16:59
-
-
Save talentdeficit/504947 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(json). | |
-export([json_to_term/2]). | |
%% extract the encoding from the options to pass to the parser, using auto if no encoding is | |
%% specified | |
json_to_term(JSON, Opts) -> | |
Encoding = proplists:get_value(encoding, Opts, auto), | |
P = jsx:parser([{encoding, Encoding}]), | |
collect(P(JSON), none, Opts). | |
%% collect is a tail recursive function that iterates over the events returned by jsx | |
%% ensure the first event we handle is start_object or start_array | |
collect({event, Start, Next}, none, Opts) when Start =:= start_object; Start =:= start_array -> | |
% the accumulator here is complex to simplify handling later, when all objects and arrrays | |
% will need a parent value to be added to, including the root object or array | |
collect(Next(), [[], []], Opts); | |
collect(_, none, _) -> | |
erlang:error(badarg); | |
%% when start_object or start_array is encountered, start a new container list at the head | |
%% of the accumulator | |
collect({event, Start, Next}, Acc, Opts) when Start =:= start_object; Start =:= start_array -> | |
collect(Next(), [[]|Acc], Opts); | |
%% special case for empty object, which has a special representation | |
collect({event, end_object, Next}, [[], Parent|Rest], Opts) -> | |
collect(Next(), [[[{}]] ++ Parent] ++ Rest, Opts); | |
%% reverse the array/object accumulator before prepending it to it's parent | |
collect({event, end_object, Next}, [Current, Parent|Rest], Opts) when is_list(Parent) -> | |
collect(Next(), [[lists:reverse(Current)] ++ Parent] ++ Rest, Opts); | |
collect({event, end_array, Next}, [Current, Parent|Rest], Opts) when is_list(Parent) -> | |
collect(Next(), [[lists:reverse(Current)] ++ Parent] ++ Rest, Opts); | |
%% special case for objects and arrays that are values of keys in a parent json object | |
collect({event, Start, Next}, [Current, Key, Parent|Rest], Opts) | |
when Start =:= end_object; Start =:= end_array -> | |
collect(Next(), [[{Key, lists:reverse(Current)}] ++ Parent] ++ Rest, Opts); | |
%% end of json marks end of parsing | |
collect({event, end_json, Next}, [[Acc]], _Opts) -> | |
Acc; | |
%% key can only be emitted inside of a json object, so just insert it directly into | |
%% the head of the accumulator and deal with it when we receive it's paired value | |
collect({event, {key, _} = PreKey, Next}, [Current|_] = Acc, Opts) -> | |
Key = event(PreKey, Opts), | |
case key_repeats(Key, Current) of | |
true -> erlang:error(badarg) | |
; false -> collect(Next(), [Key] ++ Acc, Opts) | |
end; | |
%% check acc to see if we're inside an object or an array. because inside an object | |
%% context the events that fall this far are always preceded by a key (which are | |
%% binaries or atoms), if Current is a list, we're inside an array, else, an | |
%% object | |
collect({event, Event, Next}, [Current|Rest], Opts) when is_list(Current) -> | |
collect(Next(), [[event(Event, Opts)] ++ Current] ++ Rest, Opts); | |
collect({event, Event, Next}, [Key, Current|Rest], Opts) -> | |
collect(Next(), [[{Key, event(Event, Opts)}] ++ Current] ++ Rest, Opts); | |
%% any other event is an error | |
collect(_, _, _) -> erlang:error(badarg). | |
%% formatting functions to convert from jsx representation to eep0018 representation | |
%% strings are converted to utf8 binaries | |
event({string, String}, _Opts) -> | |
unicode:characters_to_binary(String); | |
%% keys are converted to utf8 binaries, atoms (if possible) or existing atoms (if possible) | |
event({key, Key}, Opts) -> | |
case proplists:get_value(label, Opts, binary) of | |
binary -> unicode:characters_to_binary(Key) | |
; atom -> | |
try list_to_atom(Key) | |
catch error:badarg -> unicode:characters_to_binary(Key) end | |
; existing_atom -> | |
try list_to_existing_atom(Key) | |
catch error:badarg -> unicode:characters_to_binary(Key) end | |
end; | |
%% special case for negative zero | |
event({integer, "-0"}, _Opts) -> | |
erlang:float(erlang:list_to_integer("-0")); | |
%% numbers are converted to their erlang equivalent or erlang floats if the option {float, true} | |
%% is present | |
event({integer, Integer}, Opts) -> | |
case proplists:get_value(float, Opts, false) of | |
true -> erlang:float(erlang:list_to_integer(Integer)) | |
; false -> erlang:list_to_integer(Integer) | |
end; | |
event({float, Float}, _Opts) -> | |
erlang:list_to_float(Float); | |
%% literals (true, false or null) are left as atoms | |
event({literal, Literal}, _Opts) -> | |
Literal. | |
%% helper function to determine if a key has already been encountered (which is an error) | |
key_repeats(Key, [{Key, _Value}|_Rest]) -> true; | |
key_repeats(Key, [_|Rest]) -> key_repeats(Key, Rest); | |
key_repeats(_Key, []) -> false. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment