Skip to content
This repository was archived by the owner on Jan 12, 2023. It is now read-only.

Commit 04fb8fa

Browse files
committed
Merge pull request #25 from jonasrichard/dev
Refactoring, support "error" in field names
2 parents 2792e39 + b19a3f2 commit 04fb8fa

8 files changed

Lines changed: 87 additions & 185 deletions

File tree

README.md

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -144,28 +144,7 @@ In the 3rd parameter of the type definition we can write an option list with whi
144144

145145
#### Proplists to objects
146146

147-
The keys in proplist will be camel case converted and those will be the name of the attribute in the JSON object. In order that the decoder can create proplist from an object a `__type` property will be added as an extra.
148-
149-
```erlang
150-
-json({square, {number, side}}).
151-
-json({shapes, {list, data}}).
152-
-json({canvas, {proplist, opts}).
153-
154-
to_json({canvas, [{width, 500}, {height, 300}, {bit_depth: 16}]}).
155-
```
156-
157-
It is a simple object
158-
159-
```json
160-
{
161-
"__type": "proplist",
162-
"width": 500,
163-
"height": 300,
164-
"bitDepth": 16
165-
}
166-
```
167-
168-
Proplist is not a ready feature, since when property values are not numbers it doesn't work.
147+
In older version there was some proplist support but it was limited and the encoding/decoding wasn't clear. In the new version proplist can be converted by a generic rule, but the conversion functions need to be implemented by the developer (since there is no clear generic algorithm how to convert proplist to json).
169148

170149
#### Transient fields
171150

src/ejson_decode.erl

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
-export([decode/2,
2424
decode/3]).
2525

26-
-include_lib("eunit/include/eunit.hrl").
27-
2826
%% TODO:
2927
%% exact_value should return with {ok, Value} or {error, Reason}, so
3028
%% every time when we extract, we also need to case pattern match.
@@ -104,7 +102,7 @@ extract_fields([Field | F], AttrList, Opts) ->
104102
case extract_value(Field, Value, Opts) of
105103
{error, _} = Error ->
106104
Error;
107-
Extracted ->
105+
{ok, Extracted} ->
108106
case maybe_post_process(Field, Extracted) of
109107
{ok, NewVal} ->
110108
[NewVal | extract_fields(F, AttrList, Opts)];
@@ -145,89 +143,84 @@ extract_value(Rule, Value, Opts) ->
145143
extract_list(Value, [], Opts);
146144
{list, _, FieldOpts} ->
147145
extract_list(Value, FieldOpts, Opts);
148-
{generic, _, _EncFun, DecFun} ->
149-
extract_generic(Value, DecFun);
150-
{proplist, _} ->
151-
%% TODO proper conversion here!
152-
undefined;
146+
{generic, _Name, _FieldOpts} ->
147+
%% Let the post processor function makes the conversion
148+
{ok, Value};
153149
{const, _, _} ->
154-
undefined
150+
{ok, undefined}
155151
end.
156152

157153
extract_atom(null) ->
158-
undefined;
154+
{ok, undefined};
159155
extract_atom(Value) ->
160-
binary_to_atom(Value, utf8).
156+
{ok, binary_to_atom(Value, utf8)}.
161157

162158
extract_binary(null) ->
163-
undefined;
159+
{ok, undefined};
164160
extract_binary(Value) ->
165-
Value.
161+
{ok, Value}.
166162

167163
extract_boolean(null) ->
168-
undefined;
164+
{ok, undefined};
169165
extract_boolean(Value) ->
170-
Value.
166+
{ok, Value}.
171167

172168
extract_number(null) ->
173-
undefined;
169+
{ok, undefined};
174170
extract_number(Value) ->
175-
Value.
171+
{ok, Value}.
176172

177173
extract_string(null) ->
178-
undefined;
174+
{ok, undefined};
179175
extract_string(Value) ->
180-
unicode:characters_to_list(Value, utf8).
176+
{ok, unicode:characters_to_list(Value, utf8)}.
181177

182178
extract_record(null, FieldOpts, Opts) ->
183179
case proplists:get_value(default, FieldOpts) of
184180
undefined ->
185-
undefined;
181+
{ok, undefined};
186182
Default ->
187183
extract_record(Default, FieldOpts, Opts)
188184
end;
189185
extract_record(Value, FieldOpts, Opts) ->
190186
case proplists:get_value(type, FieldOpts) of
191187
undefined ->
192-
decode1(Value, Opts);
188+
{ok, decode1(Value, Opts)};
193189
Type ->
194-
decode1(Value, Opts, Type)
190+
{ok, decode1(Value, Opts, Type)}
195191
end.
196192

197193
extract_list(null, _FieldOpts, _Opts) ->
198-
undefined;
194+
{ok, undefined};
199195
extract_list(Value, FieldOpts, Opts) ->
200196
case proplists:get_value(type, FieldOpts) of
201197
undefined ->
202198
%% No target type for list element, it can be an attrlist
203199
%% or a primitive value
204-
lists:map(
200+
L = lists:map(
205201
fun(V) when is_list(V) ->
206202
{ok, D} = decode(V, Opts),
207203
%% TODO make an error case and gives back error
208204
D;
209205
(V) ->
210206
V
211-
end, Value);
207+
end, Value),
208+
{ok, L};
212209
Type ->
213-
[decode1(V, Opts, Type) || V <- Value]
214-
end.
215-
216-
extract_generic(Value, {M, F}) ->
217-
try erlang:apply(M, F, [Value]) of
218-
Val ->
219-
Val
220-
catch
221-
E:R ->
222-
{error, {field_run, {M, F}, {E, R}}}
210+
{ok, [decode1(V, Opts, Type) || V <- Value]}
223211
end.
224212

225213
maybe_post_process({const, _Name, _Const}, Value) ->
226214
{ok, Value};
227-
maybe_post_process({_Type, Name, FieldOpts}, Value) ->
215+
maybe_post_process({Type, Name, FieldOpts}, Value) ->
228216
case lists:keyfind(post_decode, 1, FieldOpts) of
229217
false ->
230-
{ok, Value};
218+
case Type of
219+
generic ->
220+
{error, {no_post_decode, Name, Value}};
221+
_ ->
222+
{ok, Value}
223+
end;
231224
{post_decode, {M, F}} ->
232225
try erlang:apply(M, F, [Value]) of
233226
NewVal ->

src/ejson_encode.erl

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,20 @@ convert([], _Tuple, _Opts, Result) ->
9999
convert([{Name, Value} | T], Tuple, Opts, Result) ->
100100
case maybe_pre_process(Name, Tuple, Value) of
101101
{ok, PreProcessed} ->
102-
case apply_rule(Name, Tuple, PreProcessed, Opts) of
102+
case apply_rule(Name, PreProcessed, Opts) of
103103
undefined ->
104104
convert(T, Tuple, Opts, Result);
105105
{error, _} = Error ->
106106
Error;
107-
{NewName, NewValue} ->
107+
{ok, {NewName, NewValue}} ->
108108
convert(T, Tuple, Opts, [{?BIN(NewName), NewValue} | Result])
109109
end;
110110
{error, _} = Error2 ->
111111
Error2
112112
end.
113113

114114
%% Generate jsx attribute from ejson field
115-
apply_rule(Name, Tuple, Value, Opts) ->
115+
apply_rule(Name, Value, Opts) ->
116116
case Name of
117117
skip ->
118118
undefined;
@@ -144,90 +144,77 @@ apply_rule(Name, Tuple, Value, Opts) ->
144144
list_rule(AttrName, Value, Opts);
145145
{list, AttrName, _FieldOpts} ->
146146
list_rule(AttrName, Value, Opts);
147-
{generic, AttrName, EncFun, _DecFun} ->
148-
generic_rule(AttrName, EncFun, Tuple, Value);
149-
{proplist, AttrName} ->
150-
proplist_rule(AttrName, Value);
147+
{generic, AttrName, _FieldOpts} ->
148+
%% Generic encoding is handled in pre_process phase
149+
{ok, {AttrName, Value}};
151150
{const, AttrName, Const} ->
152-
{AttrName, encode1(Const, Opts)};
151+
{ok, {AttrName, encode1(Const, Opts)}};
153152
AttrName ->
154153
{error, {invalid_field_rule, AttrName, Name}}
155154
end.
156155

157156
boolean_rule(AttrName, undefined) ->
158-
{AttrName, null};
157+
{ok, {AttrName, null}};
159158
boolean_rule(AttrName, Value) when is_boolean(Value) ->
160-
{AttrName, Value};
159+
{ok, {AttrName, Value}};
161160
boolean_rule(AttrName, Value) ->
162161
{error, {boolean_value_expected, AttrName, Value}}.
163162

164163
number_rule(AttrName, undefined) ->
165-
{AttrName, null};
164+
{ok, {AttrName, null}};
166165
number_rule(AttrName, Value) when is_number(Value) ->
167-
{AttrName, Value};
166+
{ok, {AttrName, Value}};
168167
number_rule(AttrName, Value) ->
169168
{error, {numeric_value_expected, AttrName, Value}}.
170169

171170
atom_rule(AttrName, undefined) ->
172-
{AttrName, null};
171+
{ok, {AttrName, null}};
173172
atom_rule(AttrName, Value) when is_atom(Value) ->
174-
{AttrName, atom_to_binary(Value, utf8)};
173+
{ok, {AttrName, atom_to_binary(Value, utf8)}};
175174
atom_rule(AttrName, Value) ->
176175
{error, {atom_value_expected, AttrName, Value}}.
177176

178177
binary_rule(AttrName, undefined) ->
179-
{AttrName, null};
178+
{ok, {AttrName, null}};
180179
binary_rule(AttrName, Value) when is_binary(Value) ->
181-
{AttrName, Value};
180+
{ok, {AttrName, Value}};
182181
binary_rule(AttrName, Value) ->
183182
{error, {binary_value_expected, AttrName, Value}}.
184183

185184
string_rule(AttrName, undefined) ->
186-
{AttrName, null};
185+
{ok, {AttrName, null}};
187186
string_rule(AttrName, Value) when is_list(Value) ->
188-
{AttrName, unicode:characters_to_binary(Value)};
187+
{ok, {AttrName, unicode:characters_to_binary(Value)}};
189188
string_rule(AttrName, Value) ->
190189
{error, {string_value_expected, AttrName, Value}}.
191190

192191
record_rule(AttrName, undefined, _FieldOpts, _Opts) ->
193-
{AttrName, null};
192+
{ok, {AttrName, null}};
194193
record_rule(AttrName, Value, _FieldOpts, Opts) when is_tuple(Value) ->
195-
{AttrName, encode1(Value, Opts)};
194+
{ok, {AttrName, encode1(Value, Opts)}};
196195
record_rule(AttrName, Value, _FieldOpts, _Opts) ->
197196
{error, {record_value_expected, AttrName, Value}}.
198197

199198
list_rule(AttrName, undefined, _Opts) ->
200-
{AttrName, null};
199+
{ok, {AttrName, null}};
201200
list_rule(AttrName, Value, Opts) when is_list(Value) ->
202201
List = [encode1(V, Opts) || V <- Value],
203-
{AttrName, List};
202+
{ok, {AttrName, List}};
204203
list_rule(AttrName, Value, _Opts) ->
205204
{error, {list_value_expected, AttrName, Value}}.
206205

207-
generic_rule(AttrName, {M, F}, Tuple, Value) ->
208-
try erlang:apply(M, F, [Tuple, Value]) of
209-
Val ->
210-
{AttrName, Val}
211-
catch
212-
E:R ->
213-
{error, {generic, {M, F}, {E, R}}}
214-
end.
215-
216-
proplist_rule(AttrName, Value) ->
217-
Vals = lists:map(
218-
fun({Prop, Val}) ->
219-
{ejson_util:atom_to_binary_cc(Prop), Val};
220-
(Prop) ->
221-
{ejson_util:atom_to_binary_cc(Prop), true}
222-
end, Value),
223-
{AttrName, [{<<"__type">>, <<"proplist">>} | Vals]}.
224-
225206
maybe_pre_process({const, _Name, _Const}, _Tuple, Value) ->
226207
{ok, Value};
227-
maybe_pre_process({_Type, Name, FieldOpts}, Tuple, Value) ->
208+
maybe_pre_process({Type, Name, FieldOpts}, Tuple, Value) ->
228209
case lists:keyfind(pre_encode, 1, FieldOpts) of
229210
false ->
230-
{ok, Value};
211+
case Type of
212+
generic ->
213+
%% In case of generic pre_encode is mandatory
214+
{error, {no_pre_encode, Name, Value}};
215+
_ ->
216+
{ok, Value}
217+
end;
231218
{pre_encode, {M, F}} ->
232219
try erlang:apply(M, F, [Tuple, Value]) of
233220
Val ->

src/ejson_util.erl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,7 @@ get_field_name({Type, Field, _FieldOpts}) when Type =:= atom orelse
7878
Type =:= record orelse
7979
Type =:= string ->
8080
Field;
81-
get_field_name({field_fun, Field, _EncFun, _DecFun}) ->
82-
Field;
83-
get_field_name({field_fun, Field, _EncFun, _DecFun, _FieldOpts}) ->
84-
Field;
85-
get_field_name({rec_fun, Field, _EncFun}) ->
81+
get_field_name({generic, Field, _FieldOpts}) ->
8682
Field;
8783
get_field_name(Field) ->
8884
{error, {invalid_field_rule, Field}}.

test/ejson_basic_test.erl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ pre_post_callback_test_() ->
3535
{ok, D} = ejson_decode:decode(E, Opts),
3636
?_assertEqual(Record, D).
3737

38+
to_jsx(_Tuple, {High, Low}) ->
39+
[{<<"high">>, High}, {<<"low">>, Low}].
40+
41+
from_jsx(Attrs) ->
42+
{_, High} = lists:keyfind(<<"high">>, 1, Attrs),
43+
{_, Low} = lists:keyfind(<<"low">>, 1, Attrs),
44+
{High, Low}.
45+
46+
generic_test_() ->
47+
Opts = [{item, {generic, count, [{pre_encode, {?MODULE, to_jsx}},
48+
{post_decode, {?MODULE, from_jsx}}]}}],
49+
Record = {item, {15, 2}},
50+
{ok, E} = ejson_encode:encode(Record, Opts),
51+
?debugVal(E),
52+
{ok, D} = ejson_decode:decode(E, Opts),
53+
?_assertEqual(Record, D).
54+
3855
-define(TYPE(Val, Opts, Err),
3956
?_assertEqual(Exp(Val, Err), Enc(Val, Opts))).
4057

test/ejson_error.erl

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@ no_record_test_() ->
99
?_assertEqual({error, {no_such_record, person}},
1010
ejson:to_json_modules({person, "Joe"}, [?MODULE])).
1111

12-
proplist_test_() ->
13-
Rec = {rec, [{p1, 10}, {p2, false}, {p3, 4.5}]},
14-
{ok, Enc} = ejson_encode:encode(Rec, [{rec, {proplist, "test"}}]),
15-
{_, Props} = lists:keyfind(<<"test">>, 1, Enc),
16-
[?_assert(lists:member({<<"p1">>, 10}, Props)),
17-
?_assert(lists:member({<<"p2">>, false}, Props)),
18-
?_assert(lists:member({<<"p3">>, 4.5}, Props))].
19-
2012
duplicate_record_test_() ->
2113
?_assertEqual({error, {duplicate_records, [a]}},
2214
ejson_encode:encode(1, [{a, b}, {a, {atom, c}}, {a, d}])).
@@ -29,7 +21,14 @@ duplicate_field_test_() ->
2921
end,
3022
[
3123
?_assert(F([{rec, {string, a}, {binary, a}}])),
32-
?_assert(F([{rec, {number, a}, {rec_fun, a, f}}])),
33-
?_assert(F([{rec, {boolean, a}, {field_fun, a, ff, ff2}}])),
24+
?_assert(F([{rec, {number, a}, {list, a}}])),
25+
?_assert(F([{rec, {boolean, a}, {generic, a, []}}])),
3426
?_assert(F([{rec, {const, a, 1}, {list, a}}]))
3527
].
28+
29+
error_conflict_test_() ->
30+
Opts = [{message, {string, error, [{default, "No errors"}]}}],
31+
Record = {message, "Syntax error"},
32+
{ok, E} = ejson_encode:encode(Record, Opts),
33+
{ok, D} = ejson_decode:decode(E, Opts),
34+
?_assertEqual(Record, D).

0 commit comments

Comments
 (0)