Skip to content

Examples

Eric Pailleau edited this page Apr 29, 2018 · 20 revisions

Examples

Convert JSON object to record specification

Create a record definition file from a JSON sample :

1> A = jason:decode_file("priv/ex1.json",[{mode, record}, {to, "/tmp/records.hrl"}]).
{'22207878',{'34707836',"example glossary",
                            {'6257036',"S",
                                       {'131402670',{'49933946',"SGML","SGML",
                                                                "Standard Generalized Markup Language","SGML",
                                                                "ISO 8879:1986",
                                                                {'111785629',"A meta-markup language, used to create markup languages such as DocBook.",
                                                                             ["GML","XML"]},
                                                                "markup"}}}}}

Looking at content of record definition file :

$ cat /tmp/records.hrl
-record('111785629', {para  = []  :: list(), 'GlossSeeAlso'  = []  :: list()}).
-record('49933946', {'ID'  = []  :: list(), 'SortAs'  = []  :: list(), 'GlossTerm'  = []  :: list(), 'Acronym'  = []  :: list(), 'Abbrev'  = []  :: list(), 'GlossDef'  = '111785629':new()  :: '111785629':'111785629'(), 'GlossSee'  = []  :: list()}).
-record('131402670', {'GlossEntry'  = '49933946':new()  :: '49933946':'49933946'()}).
-record('6257036', {title  = []  :: list(), 'GlossList'  = '131402670':new()  :: '131402670':'131402670'()}).
-record('34707836', {title  = []  :: list(), 'GlossDiv'  = '6257036':new()  :: '6257036':'6257036'()}).
-record('22207878', {glossary  = '34707836':new()  :: '34707836':'34707836'()}).

Now edit a sample module :

-module(test).

-include("/tmp/records.hrl").

-export([test/1]).

test(File) -> A = jason:decode_file(File, [{mode, record}]),
              io:format("~p~n", [A#'22207878'.glossary#'34707836'.'GlossDiv'#'6257036'.'GlossList'#'131402670'.'GlossEntry'#'49933946'.'GlossTerm']).

Compile and test module :

1> c(test).
{ok,test}
2> test:test("git/jason/priv/ex1.json").
"Standard Generalized Markup Language"
ok

Renaming record definition

Dynamic names of argonaut modules are hard to remember. If JSON structure is stable, we can rename records for simpler names and tell jason to use thoses. But be aware that jason won't create argonaut helper modules in such case.

First let's rename records in record definition (using dummy a,b,c,etc.. names for this example, but meaningful names can be used obviously). Note that argonaut opaque types need to be removed from declaration, as no argonaut modules will be created. Jason will only trust your record declaration.

-record('f', {para  = []  :: list(), 'GlossSeeAlso'  = []  :: list()}).
-record('e', {'ID'  = []  :: list(), 'SortAs'  = []  :: list(), 'GlossTerm'  = []  :: list(), 'Acronym'  = []  :: list(), 'Abbrev'  = []  :: list(), 'GlossDef'  = #'f'{}, 'GlossSee'  = []  :: list()}).
-record('d', {'GlossEntry'  = #'e'{}}).
-record('c', {title  = []  :: list(), 'GlossList'  = #'d'{}}).
-record('b', {title  = []  :: list(), 'GlossDiv'  = #'c'{}}).
-record('a', {glossary  = #'b'{}}).

Then let's modify our test module :

-module(test).

-include("/tmp/records.hrl").

-export([test/1]).

test(File) -> 
              A = jason:decode_file(File, [{mode, record}, {records, [?MODULE ]}]),
              io:format("~p~n", [A#'a'.glossary#'b'.'GlossDiv'#'c'.'GlossList'#'d'.'GlossEntry'#'e'.'GlossTerm']).

NOTE: As we ask jason to extract records declaration from test module abstract code, option debug_info is given to compiler hereafter. Please ensure it will be the case in your usual compilation process (normally yes with usual build tools).

1> c(test,[debug_info]).
{ok,test}
2> test:test("priv/ex1.json").
"Standard Generalized Markup Language"
ok

JSON to records translation when source is not stable

Let use a JSON source giving temperature of some cities, coming from third party, from an HTTP API for instance :

weather.json

[
 { "city" : "Paris" ,
   "temperature" : 15.6,
   "unit" : "celcius"
 },
 { "city" : "Berlin" ,
   "temperature" : 13.6 ,
   "unit" : "celcius"
 },
 { "city" : "London" ,
   "temperature" : 12.3 ,
   "unit" : "celcius"
 }
]

This API is announced to be possibly changing, by adding some other informations. Using records in Erlang would be a problem if those ones have to be declared in code. Use of jason and some coding precaution solve this problem :

-module(test).

-export([test/1]).

test(File) -> A = jason:decode_file(File, [{mode, record}]),
              lists:foreach(fun(R) -> Argonaut = element(1, R), 
                                      io:format("~w ~s~n", [Argonaut:temperature(R), 
                                                            Argonaut:unit(R)]) 
                            end, A).

But what if JSON change, by adding a new field in records ? Nothing.

weather1.json

[
 { "city" : "Paris" ,
   "temperature" : 15.6,
   "unit" : "celcius",
   "EEC" : true 
 },
 { "city" : "Berlin" ,
   "temperature" : 13.6 ,
   "unit" : "celcius",
   "EEC" : true 
 },
 { "city" : "London" ,
   "temperature" : 12.3 ,
   "unit" : "celcius",
   "EEC" : false
 }
]
1> c(test).
{ok,test}
2> test:test("/tmp/weather.json").
15.6 celcius
13.6 celcius
12.3 celcius
ok
3> test:test("/tmp/weather1.json").
15.6 celcius
13.6 celcius
12.3 celcius
ok

Jason create an argonaut module for each new JSON structure, and this argonaut module can be used dynamically in the code. Obviously, other precautions can be done by checking if temperature/1 function is existing in argonaut module. If not the case, mean that JSON source is invalid or changed too much...

Clone this wiki locally