-
Notifications
You must be signed in to change notification settings - Fork 4
Examples
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
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 those. 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 in order to use our private record definition from module itself :
-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
Since version 1.2.0, jason
can create argonaut modules from your own record names, called aliases.
Simply replace all hashes names by your own name aliases in .hrl file generated by jason
.
-record('F', {para = [] :: list(), 'GlossSeeAlso' = [] :: list()}).
-record('E', {'ID' = [] :: list(), 'SortAs' = [] :: list(), 'GlossTerm' = [] :: list(), 'Acronym' = [] :: list(), 'Abbrev' = [] :: list(), 'GlossDef' = 'f':new() :: 'F':'F'(), 'GlossSee' = [] :: list()}).
-record('D', {'GlossEntry' = 'E':new() :: 'E':'E'()}).
-record('C', {title = [] :: list(), 'GlossList' = 'D':new() :: 'D':'D'()}).
-record('B', {title = [] :: list(), 'GlossDiv' = 'C':new() :: 'C':'C'()}).
-record('A', {glossary = 'B':new() :: 'B':'B'()}).
Note : as c
module already exists in OTP, records are called with capital letter to avoid any module name clash in this example. It is up to you to check this if lowercase module names are used.
Now edit your test module to change thing accordingly :
-module(test).
-include("/tmp/records.hrl").
-export([test/1]).
test(File) ->
A = jason:decode_file(File, [{mode, record}, {aliases, [?MODULE ]}]),
io:format("~p~n", [A#'A'.glossary#'B'.'GlossDiv'#'C'.'GlossList'#'D'.'GlossEntry'#'E'.'GlossTerm']).
Let's test this module :
1> c(test,[debug_info]).
{ok,test}
2> test:test("priv/ex1.json").
"Standard Generalized Markup Language"
ok
3>'A':def().
"-record('A', {glossary = 'B':new() :: 'B':'B'()})."
Now argonaut modules are available with handy names for all your records. This can be checked by looking at your current process jason_adhoc
registry.
4> get(jason_adhoc).
['F','E','D','C','B','A']
We can decide (or not) to dump them on disk. This to avoid any module creation time overhead in future.
Either by asking to dump all modules (recommended) with jason:dump/1
or individual modules with jason:dump/2
.
Modules have to be dumped in a directory in path known by emulator for automatic loading, as any other Erlang module.
Important : Only argonaut modules from aliases
options can be dumped, all other dynamic argonaut modules cannot.
Note : if jason_adhoc
is empty (the case if jason:dump/x
is called from another Pid than the one which creates modules), jason
will search all argonaut modules in the whole system. If not, jason
will check that modules listed in jason_adhoc
are really argonaut ones.
5> jason:dump("/tmp/dump/"). % Only for testing as /tmp/ is not a safe directory (emptied at reboot)
ok
Note : Modules will be moved in usual OTP directory structure under root directory given.
$> tree /tmp/dump
/tmp/dump
├── A
│ └── ebin
│ └── A.beam
├── B
│ └── ebin
│ └── B.beam
├── C
│ └── ebin
│ └── C.beam
├── D
│ └── ebin
│ └── D.beam
├── E
│ └── ebin
│ └── E.beam
└── F
└── ebin
└── F.beam
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...