Skip to content
Browse files

Improve proplists API

- Remove erlson:to_list/1
- Rename erlson:from_list/1 to erlson:from_nested_proplist/1
- Add erlson:from_proplist/1
- Add erlson:from_nested_proplist/2
- Add tests
  • Loading branch information...
1 parent aaf59e7 commit 15a3c9a6cee2d4332e5c6edb0fd9cf60f9b0898f @alavrik committed Aug 22, 2011
Showing with 172 additions and 25 deletions.
  1. +63 −2 README.md
  2. +67 −23 src/erlson.erl
  3. +42 −0 test/erlson_tests.erl
View
65 README.md
@@ -12,8 +12,8 @@ Examples:
% create an empty dictionary
X = #{},
- % associate field 'foo' with 1 and 'bar' with "abc"
- D = #{foo = 1, bar = "abc"},
+ % associate fields 'foo' with 1, 'bar' with "abc" and 'fum' with 'true'
+ D = #{foo = 1, bar = "abc", fum},
% access dictionary element
1 = D.foo,
@@ -34,6 +34,18 @@ Examples:
% create Erlson dictionary from JSON iolist()
D = erlson:from_json(Json).
+
+ ...
+
+ % create Erlson dictionary from a proplist
+ D = erlson:from_proplist(L).
+
+ % create nested Erlson dictionary from a nested proplist
+ D = erlson:from_nested_proplist(L).
+
+ % create nested Erlson dictionary from a nested proplist up to the maximum
+ % depth of 2
+ D = erlson:from_nested_proplist(L, 2).
```
General properties
@@ -82,6 +94,55 @@ model.
* JSON->Erlson->JSON conversion produces an equivalent JSON object
(fields may be reordered).
+* There is one-to-one mapping between JSON and Erlang/Erlson values:
+
+ * JSON object <-> Erlson dictionary
+ * JSON array <-> Erlang list
+ * JSON number <-> Erlang `number()` (i.e. floats and integers)
+ * JSON true | false <-> Erlang `boolean()`
+ * JSON string value <-> Erlang `binary()`
+ * JSON null <-> Erlang atom `undefined`
+
+* JSON field names are decoded using the following function:
+
+ ```erlang
+ decode_json_field_name(N) ->
+ try binary_to_existing_atom(N, utf8)
+ catch
+ error:badarg -> N
+ end.
+ ```
+
+
+Erlson and property lists
+-------------------------
+
+Property list can be converted to Erlson dictionaries using the
+`erlson:from_proplist` function and its variations.
+
+Erlson dictionaries can also be used for property lists construction. Using
+Erlson, proplists look much cleaner. For example, compare
+
+```erlang
+{application, erlson,
+ [{description, "Erlang Simple Object Notation"},
+ {vsn, git},
+ {modules, []},
+ {applications, [kernel, stdlib]},
+ {env, []}]}.
+```
+
+and
+
+```erlang
+{application, erlson,
+ #{description = "Erlang Simple Object Notation",
+ vsn = git,
+ modules = [],
+ applications = [kernel, stdlib],
+ env = []}}.
+```
+
Usage instructions
------------------
View
90 src/erlson.erl
@@ -27,7 +27,7 @@
-export([init/0]).
% public API
--export([to_list/1, from_list/1]).
+-export([from_proplist/1, from_nested_proplist/1, from_nested_proplist/2]).
-export([to_json/1, from_json/1]).
% these two functions are useful, if there's a need to call mochijson2:decode
% and mochijson2:encode separately
@@ -120,46 +120,90 @@ store_val(Name, Value, Dict) ->
orddict:store(Name, Value, Dict).
-% @doc Convert Erlson dictionary to a proplist
--spec to_list/1 :: (Dict :: orddict()) -> orddict().
-to_list(Dict) -> Dict.
+% @doc Create Erlson dictionary from a proplist
+%
+% During conversion, each atom() property is converted to {atom(), true}
+% dictionary association.
+-spec from_proplist/1 :: (List :: proplist()) -> orddict().
+
+from_proplist(L) ->
+ from_nested_proplist(L, _MaxDepth = 1).
-% @doc Create Erlson dictionary from a (possibly nested) proplist
+% @doc Create nested Erlson dictionary from a (possibly nested) proplist
%
% During conversion, each atom() property is converted to {atom(), true}
% dictionary association.
--spec from_list/1 :: (List :: proplist()) -> orddict().
+-spec from_nested_proplist/1 :: (List :: proplist()) -> orddict().
-from_list(L) ->
- try from_list_1(L)
+from_nested_proplist(L) ->
+ from_nested_proplist(L, _MaxDepth = 'undefined').
+
+
+% @doc Create nested Erlson dictionary from a (possibly nested) proplist
+%
+% Valid nested proplists up to maximum depth of `MaxDepth` will be converted to
+% Erlson dictionaries.
+%
+% `MaxDepth = 'undefined'` means no limit on the depth of nesting.
+%
+%
+% ```
+% from_nested_proplist(L) ->
+% from_nested_proplist(L, _MaxDepth = 'undefined').
+%
+% from_proplist(L) ->
+% from_nested_proplist(L, _MaxDepth = 1).
+% '''
+-spec from_nested_proplist/2 :: (
+ List :: proplist(),
+ MaxDepth :: 'undefined' | pos_integer()) -> orddict().
+
+from_nested_proplist(L, MaxDepth) ->
+ try from_proplist_1(L, init_max_depth(MaxDepth))
catch
- 'erlson_bad_list' ->
- erlang:error('erlson_bad_list', [L])
+ 'erlson_bad_proplist' ->
+ erlang:error('erlson_bad_proplist', [L])
end.
-from_list_1(L) when is_list(L) ->
+init_max_depth('undefined') -> % no limit on the depth of nesting
+ % by starting from 0 we'll never hit depth 0 when decrementing MaxDepth in
+ % from_proplist_1
+ 0;
+init_max_depth(X) -> X. % positive integer
+
+
+from_proplist_1(L, MaxDepth) when is_list(L) ->
% inserting elements to the new orddict() one by one
% XXX: use merge sort instead of insertion sort?
- lists:foldl(fun store_proplist_elem/2, _EmptyDict = [], L);
-from_list_1(_) ->
- throw('erlson_bad_list').
+ lists:foldl(
+ fun (X, Dict) -> store_proplist_elem(X, Dict, MaxDepth - 1) end,
+ _EmptyDict = [],
+ L
+ );
+from_proplist_1(_, _) ->
+ throw('erlson_bad_proplist').
-store_proplist_elem(X, Dict) when is_atom(X) ->
- store_val(X, true, Dict);
-store_proplist_elem({N, V}, Dict) when is_atom(N) ->
+store_proplist_elem({N, V}, Dict, MaxDepth) when is_atom(N) ->
Value =
- % if property value is a valid property list, convert it to a nested
- % dictionary
- try from_list_1(V)
- catch 'erlson_bad_list' -> V
+ case MaxDepth of
+ 0 -> V; % we've riched the maximum nesting depth
+ _ ->
+ % if property value is a valid property list, convert it to a
+ % nested dictionary
+ try from_proplist_1(V, MaxDepth)
+ catch 'erlson_bad_proplist' -> V
+ end
end,
store_val(N, Value, Dict);
-store_proplist_elem(_X, _Dict) ->
- throw('erlson_bad_list').
+store_proplist_elem(X, Dict, _MaxDepth) when is_atom(X) ->
+ store_val(X, true, Dict);
+
+store_proplist_elem(_X, _Dict, _MaxDepth) ->
+ throw('erlson_bad_proplist').
% @doc Convert Erlson dictionary to a JSON Object
View
42 test/erlson_tests.erl
@@ -186,3 +186,45 @@ json_null() ->
?assert(erlson:from_json(J) =:= D),
ok.
+
+proplist_test() ->
+ ?assertEqual(#{}, erlson:from_proplist([])),
+ ?assertEqual(#{}, erlson:from_nested_proplist([])),
+
+ ?assertEqual(#{foo}, erlson:from_proplist([foo])),
+ ?assertEqual(#{foo}, erlson:from_nested_proplist([foo])),
+
+ ?assertEqual(#{foo}, erlson:from_proplist([{foo, true}])),
+ ?assertEqual(#{foo}, erlson:from_nested_proplist([{foo, true}])),
+
+ L =
+ [{description, "Erlang Simple Object Notation"},
+ {vsn, git},
+ {modules, []},
+ {applications, [kernel, stdlib]},
+ {env, []}],
+ D =
+ #{description = "Erlang Simple Object Notation",
+ vsn = git,
+ modules = [],
+ applications = [kernel, stdlib],
+ env = []},
+
+ NestedD =
+ #{description = "Erlang Simple Object Notation",
+ vsn = git,
+ modules = [],
+ applications = #{kernel, stdlib},
+ env = []},
+
+ ?assertEqual(D, erlson:from_proplist(L)),
+ ?assertEqual(D, erlson:from_nested_proplist(L,1)),
+
+ ?assertEqual(NestedD, erlson:from_nested_proplist(L)),
+ ?assertEqual(NestedD, erlson:from_nested_proplist(L, 'undefined')),
+
+ ?assertEqual(NestedD, erlson:from_nested_proplist(L,2)),
+ ?assertEqual(NestedD, erlson:from_nested_proplist(L,10)),
+
+ ok.
+

0 comments on commit 15a3c9a

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