Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit 9a0c919b396c94be4cc5160441ac1ca9f4b04adf 1 parent ab757e2
@lafka authored
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.
Please sign in to comment.
Something went wrong with that request. Please try again.