Skip to content
Browse files

Make JSON marshalling return same internal structure as XML

Make the converting of mochijson struct to internal tree struct
a bit less optimistic and use the idea of DOM Node lists. This
will allow result set to be searched directly with lists:keymember.
  • Loading branch information...
1 parent ab757e2 commit 9a0c919b396c94be4cc5160441ac1ca9f4b04adf @lafka committed Sep 10, 2012
Showing with 49 additions and 34 deletions.
  1. +1 −1 src/tavern_http.erl
  2. +48 −33 src/tavern_marshal_json.erl
View
2 src/tavern_http.erl
@@ -6,7 +6,7 @@
%% HTTP parse callbacks
-export([init/3, handle/3, terminate/3, status/1]).
--type key() :: atom().
+-type key() :: atom() | binary().
-type value() :: binary() | number() | string() | tree().
-type tree() :: [{key(), value()}].
-type request_method() :: atom().
View
81 src/tavern_marshal_json.erl
@@ -7,29 +7,28 @@
-spec decode(binary()) -> {ok, Data :: tavern_http:tree()} | {error, Error :: atom()}.
decode(Payload) ->
case (catch mochijson2:decode(Payload)) of
- {struct, _} = Res -> {ok, decode_mochistruct(Res)};
+ {struct, [_]} = Res -> {ok, [decode_mochistruct(Res)]};
+ {struct, _} = Res -> {ok, decode_mochistruct(Res)};
Struct when is_list(Struct) -> {ok, decode_mochistruct(Struct)};
- _ -> {error, {'invalid json payload'}}
+ _ -> {error, 'invalid json payload'}
end.
-spec encode(tavern_http:tree()) -> {ok, Data :: iolist()} | {error, Error :: atom()}.
-encode(Payload) ->
+encode(Payload) ->
case (catch mochijson2:encode(Payload)) of
{'EXIT', _} -> {error, 'json serialization failed'};
Data -> {ok, Data}
end.
-
-%% Optimistic, we don't have a "list" in the JSON sense so we can assume
-%% a list containing a single object is actually a multi level object.
-%% This is to allow XML like <root><child>val</child></root> to be the same
-%% as both {root : {child : "val"}} AND {root : [{child : "val"}]}.
--spec decode_mochistruct({struct, [any()] | [any()]}) -> [any()].
-decode_mochistruct({struct, Struct}) -> decode_mochistruct(Struct);
-decode_mochistruct([{Key, Struct}]) -> decode_mochistruct({Key, Struct});
-decode_mochistruct({Key, Struct}) -> {Key, decode_mochistruct(Struct)};
-decode_mochistruct(List) when is_list(List) -> [decode_mochistruct(A) || A <- List];
-decode_mochistruct(Val) -> Val.
-
+%% Optimistic, we don't have a "objects" in the JSON sense, instead we have a notion
+%% similar to DOM node lists, meaning there can be no single object that's not inside
+%% a list.
+-spec decode_mochistruct({struct | binary(), [any()] | any()} | [any()]) -> [any()].
+decode_mochistruct({K, {struct, _} = V}) -> {K, [decode_mochistruct(V)]};
+decode_mochistruct({struct, [V]}) -> decode_mochistruct(V);
+decode_mochistruct({struct, V}) -> decode_mochistruct(V);
+decode_mochistruct({<<K/binary>>, V}) -> {K, decode_mochistruct(V)};
+decode_mochistruct(List) when is_list(List) -> [decode_mochistruct(S) || S <- List];
+decode_mochistruct(V) -> V.
-ifdef(TEST).
encode_single_level_test() ->
@@ -55,23 +54,39 @@ decode_mochistruct(Val) -> Val.
]}]).
decode_test() ->
- {ok, {<<"t0">>, <<"v0">>}} = decode(
- <<"[{\"t0\" : \"v0\"}]">>),
- {ok, {<<"t1">>,{<<"k2">>, <<"v2">>}}} = decode(
- <<"{\"t1\" : {\"k2\" : \"v2\"}}">>),
- {ok, {<<"t2">>,[{<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]}} = decode(
- <<"{\"t2\":[{\"k2\":\"v2\"}, {\"k3\" : \"v3\"}]}">>),
- {ok, {<<"t3">>,{<<"k2">>, <<"v2">>}}} = decode(
- <<"{\"t3\":[{\"k2\":\"v2\"}]}">>),
- {ok, {<<"t4">>,[{<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]}} = decode(
- <<"[{\"t4\":[{\"k2\":\"v2\"},{\"k3\":\"v3\"}]}]">>),
- {ok, [{<<"t5">>,{<<"k1">>, <<"v1">>}}, {<<"t5a">>, <<"v2">>}]} = decode(
- <<"[{\"t5\":{\"k1\":\"v1\"}}, {\"t5a\":\"v2\"}]">>).
+ ?assertEqual({ok, [{<<"t0a">>, <<"v0">>}]},
+ decode(<<"{\"t0a\" : \"v0\"}">>)),
+ ?assertEqual({ok, [{<<"t0b">>, <<"v0">>}]},
+ decode(<<"[{\"t0b\" : \"v0\"}]">>)),
+ ?assertEqual({ok, [{<<"t1">>,[{<<"k2">>, <<"v2">>}]}]},
+ decode(<<"{\"t1\" : {\"k2\" : \"v2\"}}">>)),
+ ?assertEqual({ok, [{<<"t2">>,[{<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]}]},
+ decode(<<"{\"t2\":[{\"k2\":\"v2\"}, {\"k3\" : \"v3\"}]}">>)),
+ ?assertEqual({ok, [{<<"t3">>,[{<<"k2">>, <<"v2">>}]}]},
+ decode(<<"{\"t3\":[{\"k2\":\"v2\"}]}">>)),
+ ?assertEqual({ok, [{<<"t4">>,[{<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]}]},
+ decode(<<"[{\"t4\":[{\"k2\":\"v2\"},{\"k3\":\"v3\"}]}]">>)),
+ ?assertEqual({ok, [{<<"t5">>,[{<<"k1">>, <<"v1">>}]}, {<<"t5a">>, <<"v2">>}]},
+ decode(<<"[{\"t5\":{\"k1\":\"v1\"}}, {\"t5a\":\"v2\"}]">>)).
+
+ decode_multiprop_object_test() ->
+ ?assertEqual({ok, [{<<"m0a">>, <<"v0a">>}, {<<"m0b">>, <<"v0b">>}]},
+ decode(<<"{\"m0a\":\"v0a\", \"m0b\" : \"v0b\"}">>)).
+
+ decode_datatypes_test() ->
+ ?assertEqual({ok, [{<<"p0">>, 1.23}]}, decode(<<"{\"p0\":1.23}">>)),
+ ?assertEqual({ok, [{<<"p1">>, 4}]}, decode(<<"{\"p1\":4}">>)),
+ ?assertEqual({ok, [{<<"p2">>, null}]}, decode(<<"{\"p2\":null}">>)),
+ ?assertEqual({ok, [{<<"p3">>, true}]}, decode(<<"{\"p3\":true}">>)),
+ ?assertEqual({ok, [{<<"p4">>, false}]}, decode(<<"{\"p4\":false}">>)),
+ {error, _} = decode(<<"{\"p5\":undefined}">>),
+ {error, _} = decode(<<"{\"p6\":ok}">>).
+
+ encode_decode_test() ->
+ ErlTerm = [{<<"level1">>, [{<<"level2">>, [{<<"level3">>, [{<<"level4">>, <<"value">>}]}]}]}],
+ {ok, JSON} = encode(ErlTerm),
+ ?assertEqual({ok, ErlTerm}, decode(JSON)).
-% encode_decode_test() ->
-% {ok, JSON} = encode([{level1, [{level2, [{level3, [{level4, <<"value">>}]}]}]}]),
-% {ok, _} = decode(JSON).
-%
-% encode_binary_key_test() ->
-% {ok, _} = encode([{<<"level1">>, [{<<"level2">>, "value"}]}]).
+ encode_atom_key_test() ->
+ {ok, _} = encode([{level1, [{level2, "value"}]}]).
-endif.

0 comments on commit 9a0c919

Please sign in to comment.
Something went wrong with that request. Please try again.