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

5. Pre encode and post decode

Richard Jonas edited this page May 17, 2016 · 4 revisions

Converting data during encoding and decoding

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 the pre_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 is log
  • 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.

For "hackers"

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.

Supporting dict with generic

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.