-
Notifications
You must be signed in to change notification settings - Fork 16
5. Pre encode and post decode
We have the most powerful tool which we need to use wisely. For almost each type of data we can pre-process values before encoding and also we can post-process values after decoding data from JSON. Let us a simple example for that. Suppose that we need to convert calendar:datetime()
to ISO date format.
%% We serialize log messages where both the message
%% and the time will be binary (string) in JSON.
-json({log, {binary, message},
{binary, time, [{pre_encode, {?MODULE, time_to_binary}},
{post_decode, {?MODULE, binary_to_time}}]
}
}).
-export([time_to_binary/2, binary_to_time/2]).
time_to_binary(_LogRecord, Time) ->
%% We got the whole record and the time before encoding
%% Time is supposed to be a calendar:datetime() format.
%% We convert something like 2011-08-28T15:41:09Z
{{Y, Mo, D}, {H, Mi, S}} = Time,
io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
[Y, Mo, D, H, Mi, S])).
binary_to_time(_Record, IsoDate) ->
%% Here we have a structure which is half-ready in the first
%% parameter. It is in jsx raw format like:
%% [{<<"attr1">>, <<"value1">>}, ...] where value part can be
%% a binary, a boolean, a number or undefined
parse_iso_to_date(IsoDate).
parse_iso_date(<<Y1, Y2, Y3, Y4, $-, Mo1, Mo2, $-, D1, D2, $T,
H1, H2, $:, Mi1, Mi2, $:, S1, S2, $Z>>) ->
{{list_to_integer([Y1, Y2, Y3, Y4]),
list_to_integer([Mo1, Mo2]),
list_to_integer([D1, D2])},
{list_to_integer([H1, H2]),
list_to_integer([Mi1, Mi2]),
list_to_integer([S1, S2])}}.
In this example the converter functions are a bit long but covers most of the input possibilities. Here is the workflow during encoding:
- we create a log record as
{log, <<"Hello">>, {{2011, 8, 28}, {15, 41, 9}}}
- call
ejson:to_json/1
to that value -
ejson
calls thepre_encode
function for that value - the intermediate result is
[{<<"__rec">>, <<"log">>}, {<<"message">>, <<"Hello">>}, {<<"time">>, <<"2011-08-28T15:41:09Z">>}]
- so the JSON can be resulted from that jsx list
During decoding:
- from JSON string jsx creates the intermediate jsx list:
[{<<"__rec">>, <<"log">>}, {<<"message">>, <<"Hello">>}, {<<"time">>, <<"2011-08-28T15:41:09Z">>}]
-
ejson
knows from the value of<<"__rec">>
that the target type islog
- applies normal decoding process, so we will have a
{log, <<"Hello">>, <<"2011-08-28T15:41:09Z">>}
record - applies
post_decode
function for the ISO date value, so the time will be{{2011, 8, 28}, {15, 41, 9}}
- and we get the record we have in the beginning.
In the encoding process pre_encode({log, <<"Hello">>, <<"2011-08-28T15:41:09Z">>})
is called and during decoding post_decode([{<<"__rec">>, <<"log">>}, {<<"message">>, <<"Hello">>}], <<"2011-08-28T15:41:09Z">>)
. Let us note that the first parameter of post_decode is not complete but at that step that is all we have collected from the JSON so far.
Let us say you want to support dict values. In that case we cannot say that that value has a type of number or binary, so we need to tell ejson that that value is generic. For generic values we always apply pre_encode and post_decode and we always provide jsx list and get jsx list to create our data type.
-json({config, {generic, dictionary, [{pre_encode, {?MODULE, dict_to_jsx}},
{post_decode, {?MODULE, jsx_to_dict}}]
}
}).
%% Let us say that config records are like:
%% {config, Dict} where Dict is dict of binary() keys and binary() values
%% So we will create a json object from the dict where attribute names
%% will be keys, and values will be values
dict_to_jsx(_Record, Dict) ->
dict:fold(fun(K, V, Acc) ->
[{K, V} | Acc]
end, [], Dict).
jsx_to_dict(_JsxList, JsxDict) ->
lists:foldl(fun({K, V}, Acc) ->
dict:store(K, V, Acc)
end, dict:new(), JsxDict).
So if you understand how ejson
works with jsx
library, you will master JSON converting with ejson
.