You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

459 line
16 KiB

  1. %% The MIT License
  2. %% Copyright (c) 2010-2013 Alisdair Sullivan <alisdairsullivan@yahoo.ca>
  3. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  4. %% of this software and associated documentation files (the "Software"), to deal
  5. %% in the Software without restriction, including without limitation the rights
  6. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. %% copies of the Software, and to permit persons to whom the Software is
  8. %% furnished to do so, subject to the following conditions:
  9. %% The above copyright notice and this permission notice shall be included in
  10. %% all copies or substantial portions of the Software.
  11. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  17. %% THE SOFTWARE.
  18. -module(jsx_to_term).
  19. -export([to_term/2]).
  20. -export([init/1, handle_event/2]).
  21. -export([
  22. start_term/1,
  23. start_object/1,
  24. start_array/1,
  25. finish/1,
  26. insert/2,
  27. get_key/1,
  28. get_value/1
  29. ]).
  30. -record(config, {
  31. labels = binary,
  32. return_maps = false
  33. }).
  34. -type config() :: list().
  35. -export_type([config/0]).
  36. -ifndef(maps_support).
  37. -type json_value() :: list(json_value())
  38. | list({binary() | atom(), json_value()}) | [{},...]
  39. | true
  40. | false
  41. | null
  42. | integer()
  43. | float()
  44. | binary().
  45. -endif.
  46. -ifdef(maps_support).
  47. -type json_value() :: list(json_value())
  48. | list({binary() | atom(), json_value()}) | [{},...]
  49. | map()
  50. | true
  51. | false
  52. | null
  53. | integer()
  54. | float()
  55. | binary().
  56. -endif.
  57. -spec to_term(Source::binary(), Config::config()) -> json_value().
  58. -ifdef(maps_always).
  59. to_term(Source, Config) when is_list(Config) ->
  60. (jsx:decoder(?MODULE, [return_maps] ++ Config, jsx_config:extract_config(Config)))(Source).
  61. -endif.
  62. -ifndef(maps_always).
  63. to_term(Source, Config) when is_list(Config) ->
  64. (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source).
  65. -endif.
  66. parse_config(Config) -> parse_config(Config, #config{}).
  67. parse_config([{labels, Val}|Rest], Config)
  68. when Val == binary; Val == atom; Val == existing_atom; Val == attempt_atom ->
  69. parse_config(Rest, Config#config{labels = Val});
  70. parse_config([labels|Rest], Config) ->
  71. parse_config(Rest, Config#config{labels = binary});
  72. parse_config([{return_maps, Val}|Rest], Config)
  73. when Val == true; Val == false ->
  74. parse_config(Rest, Config#config{return_maps = Val});
  75. parse_config([return_maps|Rest], Config) ->
  76. parse_config(Rest, Config#config{return_maps = true});
  77. parse_config([{K, _}|Rest] = Options, Config) ->
  78. case lists:member(K, jsx_config:valid_flags()) of
  79. true -> parse_config(Rest, Config)
  80. ; false -> erlang:error(badarg, [Options, Config])
  81. end;
  82. parse_config([K|Rest] = Options, Config) ->
  83. case lists:member(K, jsx_config:valid_flags()) of
  84. true -> parse_config(Rest, Config)
  85. ; false -> erlang:error(badarg, [Options, Config])
  86. end;
  87. parse_config([], Config) ->
  88. Config.
  89. -type state() :: {list(), #config{}}.
  90. -spec init(Config::proplists:proplist()) -> state().
  91. init(Config) -> start_term(Config).
  92. -spec handle_event(Event::any(), State::state()) -> state().
  93. handle_event(end_json, State) -> get_value(State);
  94. handle_event(start_object, State) -> start_object(State);
  95. handle_event(end_object, State) -> finish(State);
  96. handle_event(start_array, State) -> start_array(State);
  97. handle_event(end_array, State) -> finish(State);
  98. handle_event({key, Key}, {_, Config} = State) -> insert(format_key(Key, Config), State);
  99. handle_event({_, Event}, State) -> insert(Event, State).
  100. format_key(Key, Config) ->
  101. case Config#config.labels of
  102. binary -> Key
  103. ; atom -> binary_to_atom(Key, utf8)
  104. ; existing_atom -> binary_to_existing_atom(Key, utf8)
  105. ; attempt_atom ->
  106. try binary_to_existing_atom(Key, utf8) of
  107. Result -> Result
  108. catch
  109. error:badarg -> Key
  110. end
  111. end.
  112. %% internal state is a stack and a config object
  113. %% `{Stack, Config}`
  114. %% the stack is a list of in progress objects/arrays
  115. %% `[Current, Parent, Grandparent,...OriginalAncestor]`
  116. %% an object has the representation on the stack of
  117. %% `{object, [
  118. %% {NthKey, NthValue},
  119. %% {NMinus1Key, NthMinus1Value},
  120. %% ...,
  121. %% {FirstKey, FirstValue}
  122. %% ]}`
  123. %% or if returning maps
  124. %% `{object, #{
  125. %% FirstKey => FirstValue,
  126. %% SecondKey => SecondValue,
  127. %% ...,
  128. %% NthKey => NthValue
  129. %% }}`
  130. %% or if there's a key with a yet to be matched value
  131. %% `{object, Key, ...}`
  132. %% an array looks like
  133. %% `{array, [NthValue, NthMinus1Value,...FirstValue]}`
  134. start_term(Config) when is_list(Config) -> {[], parse_config(Config)}.
  135. -ifndef(maps_support).
  136. %% allocate a new object on top of the stack
  137. start_object({Stack, Config}) -> {[{object, []}] ++ Stack, Config}.
  138. %% allocate a new array on top of the stack
  139. start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
  140. %% finish an object or array and insert it into the parent object if it exists or
  141. %% return it if it is the root object
  142. finish({[{object, []}], Config}) -> {[{}], Config};
  143. finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
  144. finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
  145. finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
  146. finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
  147. finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
  148. finish(_) -> erlang:error(badarg).
  149. %% insert a value when there's no parent object or array
  150. insert(Value, {[], Config}) -> {Value, Config};
  151. %% insert a key or value into an object or array, autodetects the 'right' thing
  152. insert(Key, {[{object, Pairs}|Rest], Config}) ->
  153. {[{object, Key, Pairs}] ++ Rest, Config};
  154. insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
  155. {[{object, [{Key, Value}] ++ Pairs}] ++ Rest, Config};
  156. insert(Value, {[{array, Values}|Rest], Config}) ->
  157. {[{array, [Value] ++ Values}] ++ Rest, Config};
  158. insert(_, _) -> erlang:error(badarg).
  159. -endif.
  160. -ifdef(maps_support).
  161. %% allocate a new object on top of the stack
  162. start_object({Stack, Config=#config{return_maps=true}}) ->
  163. {[{object, #{}}] ++ Stack, Config};
  164. start_object({Stack, Config}) ->
  165. {[{object, []}] ++ Stack, Config}.
  166. %% allocate a new array on top of the stack
  167. start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
  168. %% finish an object or array and insert it into the parent object if it exists or
  169. %% return it if it is the root object
  170. finish({[{object, Map}], Config=#config{return_maps=true}}) -> {Map, Config};
  171. finish({[{object, Map}|Rest], Config=#config{return_maps=true}}) -> insert(Map, {Rest, Config});
  172. finish({[{object, []}], Config}) -> {[{}], Config};
  173. finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
  174. finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
  175. finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
  176. finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
  177. finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
  178. finish(_) -> erlang:error(badarg).
  179. %% insert a value when there's no parent object or array
  180. insert(Value, {[], Config}) -> {Value, Config};
  181. %% insert a key or value into an object or array, autodetects the 'right' thing
  182. insert(Key, {[{object, Map}|Rest], Config=#config{return_maps=true}}) ->
  183. {[{object, Key, Map}] ++ Rest, Config};
  184. insert(Key, {[{object, Pairs}|Rest], Config}) ->
  185. {[{object, Key, Pairs}] ++ Rest, Config};
  186. insert(Value, {[{object, Key, Map}|Rest], Config=#config{return_maps=true}}) ->
  187. {[{object, maps:put(Key, Value, Map)}] ++ Rest, Config};
  188. insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
  189. {[{object, [{Key, Value}] ++ Pairs}] ++ Rest, Config};
  190. insert(Value, {[{array, Values}|Rest], Config}) ->
  191. {[{array, [Value] ++ Values}] ++ Rest, Config};
  192. insert(_, _) -> erlang:error(badarg).
  193. -endif.
  194. get_key({[{object, Key, _}|_], _}) -> Key;
  195. get_key(_) -> erlang:error(badarg).
  196. get_value({Value, _Config}) -> Value;
  197. get_value(_) -> erlang:error(badarg).
  198. %% eunit tests
  199. -ifdef(TEST).
  200. -include_lib("eunit/include/eunit.hrl").
  201. config_test_() ->
  202. [
  203. {"empty config", ?_assertEqual(#config{}, parse_config([]))},
  204. {"implicit binary labels", ?_assertEqual(#config{}, parse_config([labels]))},
  205. {"binary labels", ?_assertEqual(#config{}, parse_config([{labels, binary}]))},
  206. {"atom labels", ?_assertEqual(#config{labels=atom}, parse_config([{labels, atom}]))},
  207. {"existing atom labels", ?_assertEqual(
  208. #config{labels=existing_atom},
  209. parse_config([{labels, existing_atom}])
  210. )},
  211. {"return_maps true", ?_assertEqual(
  212. #config{return_maps=true},
  213. parse_config([return_maps])
  214. )},
  215. {"invalid opt flag", ?_assertError(badarg, parse_config([error]))},
  216. {"invalid opt tuple", ?_assertError(badarg, parse_config([{error, true}]))}
  217. ].
  218. format_key_test_() ->
  219. [
  220. {"binary key", ?_assertEqual(<<"key">>, format_key(<<"key">>, #config{labels=binary}))},
  221. {"atom key", ?_assertEqual(key, format_key(<<"key">>, #config{labels=atom}))},
  222. {"existing atom key", ?_assertEqual(
  223. key,
  224. format_key(<<"key">>, #config{labels=existing_atom})
  225. )},
  226. {"nonexisting atom key", ?_assertError(
  227. badarg,
  228. format_key(<<"nonexistentatom">>, #config{labels=existing_atom})
  229. )},
  230. {"sloppy existing atom key", ?_assertEqual(
  231. key,
  232. format_key(<<"key">>, #config{labels=attempt_atom})
  233. )},
  234. {"nonexisting atom key", ?_assertEqual(
  235. <<"nonexistentatom">>,
  236. format_key(<<"nonexistentatom">>, #config{labels=attempt_atom})
  237. )}
  238. ].
  239. rep_manipulation_test_() ->
  240. [
  241. {"allocate a new context with option", ?_assertEqual(
  242. {[], #config{labels=atom}},
  243. start_term([{labels, atom}])
  244. )},
  245. {"allocate a new object on an empty stack", ?_assertEqual(
  246. {[{object, []}], #config{}},
  247. start_object({[], #config{}})
  248. )},
  249. {"allocate a new object on a stack", ?_assertEqual(
  250. {[{object, []}, {object, []}], #config{}},
  251. start_object({[{object, []}], #config{}})
  252. )},
  253. {"allocate a new array on an empty stack", ?_assertEqual(
  254. {[{array, []}], #config{}},
  255. start_array({[], #config{}})
  256. )},
  257. {"allocate a new array on a stack", ?_assertEqual(
  258. {[{array, []}, {object, []}], #config{}},
  259. start_array({[{object, []}], #config{}})
  260. )},
  261. {"insert a key into an object", ?_assertEqual(
  262. {[{object, key, []}, junk], #config{}},
  263. insert(key, {[{object, []}, junk], #config{}})
  264. )},
  265. {"get current key", ?_assertEqual(
  266. key,
  267. get_key({[{object, key, []}], #config{}})
  268. )},
  269. {"try to get non-key from object", ?_assertError(
  270. badarg,
  271. get_key({[{object, []}], #config{}})
  272. )},
  273. {"try to get key from array", ?_assertError(
  274. badarg,
  275. get_key({[{array, []}], #config{}})
  276. )},
  277. {"insert a value into an object", ?_assertEqual(
  278. {[{object, [{key, value}]}, junk], #config{}},
  279. insert(value, {[{object, key, []}, junk], #config{}})
  280. )},
  281. {"insert a value into an array", ?_assertEqual(
  282. {[{array, [value]}, junk], #config{}},
  283. insert(value, {[{array, []}, junk], #config{}})
  284. )},
  285. {"finish an object with no ancestor", ?_assertEqual(
  286. {[{a, b}, {x, y}], #config{}},
  287. finish({[{object, [{x, y}, {a, b}]}], #config{}})
  288. )},
  289. {"finish an empty object", ?_assertEqual(
  290. {[{}], #config{}},
  291. finish({[{object, []}], #config{}})
  292. )},
  293. {"finish an object with an ancestor", ?_assertEqual(
  294. {[{object, [{key, [{a, b}, {x, y}]}, {foo, bar}]}], #config{}},
  295. finish({[{object, [{x, y}, {a, b}]}, {object, key, [{foo, bar}]}], #config{}})
  296. )},
  297. {"finish an array with no ancestor", ?_assertEqual(
  298. {[a, b, c], #config{}},
  299. finish({[{array, [c, b, a]}], #config{}})
  300. )},
  301. {"finish an array with an ancestor", ?_assertEqual(
  302. {[{array, [[a, b, c], d, e, f]}], #config{}},
  303. finish({[{array, [c, b, a]}, {array, [d, e, f]}], #config{}})
  304. )}
  305. ].
  306. -ifdef(maps_support).
  307. rep_manipulation_with_maps_test_() ->
  308. [
  309. {"allocate a new object on an empty stack", ?_assertEqual(
  310. {[{object, #{}}], #config{return_maps=true}},
  311. start_object({[], #config{return_maps=true}})
  312. )},
  313. {"allocate a new object on a stack", ?_assertEqual(
  314. {[{object, #{}}, {object, #{}}], #config{return_maps=true}},
  315. start_object({[{object, #{}}], #config{return_maps=true}})
  316. )},
  317. {"insert a key into an object", ?_assertEqual(
  318. {[{object, key, #{}}, junk], #config{return_maps=true}},
  319. insert(key, {[{object, #{}}, junk], #config{return_maps=true}})
  320. )},
  321. {"get current key", ?_assertEqual(
  322. key,
  323. get_key({[{object, key, #{}}], #config{return_maps=true}})
  324. )},
  325. {"try to get non-key from object", ?_assertError(
  326. badarg,
  327. get_key({[{object, #{}}], #config{return_maps=true}})
  328. )},
  329. {"insert a value into an object", ?_assertEqual(
  330. {[{object, #{key => value}}, junk], #config{return_maps=true}},
  331. insert(value, {[{object, key, #{}}, junk], #config{return_maps=true}})
  332. )},
  333. {"finish an object with no ancestor", ?_assertEqual(
  334. {#{a => b, x => y}, #config{return_maps=true}},
  335. finish({[{object, #{x => y, a => b}}], #config{return_maps=true}})
  336. )},
  337. {"finish an empty object", ?_assertEqual(
  338. {#{}, #config{return_maps=true}},
  339. finish({[{object, #{}}], #config{return_maps=true}})
  340. )},
  341. {"finish an object with an ancestor", ?_assertEqual(
  342. {
  343. [{object, #{key => #{a => b, x => y}, foo => bar}}],
  344. #config{return_maps=true}
  345. },
  346. finish({
  347. [{object, #{x => y, a => b}}, {object, key, #{foo => bar}}],
  348. #config{return_maps=true}
  349. })
  350. )}
  351. ].
  352. return_maps_test_() ->
  353. [
  354. {"an empty map", ?_assertEqual(
  355. #{},
  356. jsx:decode(<<"{}">>, [return_maps])
  357. )},
  358. {"an empty map", ?_assertEqual(
  359. [{}],
  360. jsx:decode(<<"{}">>, [])
  361. )},
  362. {"an empty map", ?_assertEqual(
  363. [{}],
  364. jsx:decode(<<"{}">>, [{return_maps, false}])
  365. )},
  366. {"a small map", ?_assertEqual(
  367. #{<<"awesome">> => true, <<"library">> => <<"jsx">>},
  368. jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [return_maps])
  369. )},
  370. {"a recursive map", ?_assertEqual(
  371. #{<<"key">> => #{<<"key">> => true}},
  372. jsx:decode(<<"{\"key\": {\"key\": true}}">>, [return_maps])
  373. )},
  374. {"a map inside a list", ?_assertEqual(
  375. [#{}],
  376. jsx:decode(<<"[{}]">>, [return_maps])
  377. )}
  378. ].
  379. -endif.
  380. handle_event_test_() ->
  381. Data = jsx:test_cases(),
  382. [
  383. {
  384. Title, ?_assertEqual(
  385. Term,
  386. lists:foldl(fun handle_event/2, init([]), Events ++ [end_json])
  387. )
  388. } || {Title, _, Term, Events} <- Data
  389. ].
  390. -endif.