3
3
4
4
## Overview
5
5
6
- Rec2json is a parse transform that takes a module which defines a record of
7
- the same name and adds to_json, from_json, and introspection functions. The
8
- to_json and from_json convert to and from the
9
- [ proposed erlang json standard ] ( http://www.erlang.org/eeps/eep-0018.html )
6
+ Rec2json is a parse transform that takes a module which defines a record of
7
+ the same name and adds to_json, from_json, and introspection functions. The
8
+ to_json and from_json convert to and from the map based format used by jsx and
9
+ other json encoding and decoding libraries.
10
10
11
11
## Features
12
12
13
- * Resulting modules can be used as parameterized modules or pure erlang.
14
13
* Uses a parse transform.
15
- * Limited type checking on json -> record conversion.
16
- * Limited type checking can include user defined types.
14
+ * Type checking on json -> record conversion using primitive built-in types .
15
+ * Type checking can be extended to include user defined types.
17
16
* Atom 'undefined' fields in records optionally skipped or set to null.
18
17
* Atom 'null' in json optionally converted to 'undefined'.
19
18
* Post processing options on record -> json convertion.
20
19
* Seed json -> record conversion with a record.
21
- * Nested json -> record and record -> json conversions for other
22
- records that have been compiled using rec2json.
20
+ * Nested json -> record and record -> json conversions for other records
21
+ that have been compiled using rec2json.
23
22
* Generated module has accessor functions for fields and field_names for
24
23
list of fields in the record.
25
24
* Above feature can be surpressed, and is careful by default.
@@ -46,13 +45,11 @@ module or in an include file.
46
45
47
46
The transformed modules depend on the rec2json application's modules.
48
47
49
- The records are unchanged and can be used normally. A record can also be
50
- used as a paramterized module, making it simple to use with the
51
- [ erlydtl Django templates] ( https://github.com/evanmiller/erlydtl ) project.
48
+ The records are unchanged and can be used normally.
52
49
53
50
Options are passed to the rec2json parse transform through compile options.
54
51
The parse transform checks for the key 'rec2json' in the compile options.
55
- The value is expected to be a proplist.
52
+ The value is expected to be a proplist.
56
53
57
54
Options can also be passed in on a per-module basis by adding one or more
58
55
` rec2json ` module attributes. A ` rec2json ` module attribute can either be a
@@ -73,21 +70,21 @@ created. If set to false, they are not created nor exported.</td>
73
70
<tr>
74
71
<td>generate_setters</td> <td>true : boolean()</td> <td> If set to
75
72
true, functions for setting the fields of a record are created and
76
- exported. These are of the form Field(NewVal, Record) or
77
- Record : Field (NewVal). If set to false, they are not created nor exported.
73
+ exported. These are of the form Field(NewVal, Record). If set to false, they
74
+ are not created nor exported.
78
75
</td >
79
76
</tr >
80
77
<tr >
81
78
<td >careful</td > <td >true : boolean()</td > <td > If set to true,
82
- rec2json's parse transform avoid altering or adding functions that are
79
+ rec2json's parse transform avoids altering or adding functions that are
83
80
already defined in the module. This means you can override the default
84
81
to_json/1 function to call to_json/2 with a specific set of options.</td >
85
82
</tr >
86
83
<tr >
87
84
<td >generate_type</td > <td >true : boolean()</td > <td > If set to
88
85
true, a type is generated for the record, and that type is exported. In
89
86
addition, a function is generated so that other rec2json records using the
90
- exported type work as expected</td >
87
+ exported type work as expected. </td >
91
88
</tr >
92
89
<tr >
93
90
<td >type_name</td > <td > ?MODULE : atom()</td > <td > If generate_type is
@@ -112,9 +109,8 @@ Record = #person{ name = <<"John">>, age = 32, spouse = undefined }.
112
109
To convert a record to a json structure:
113
110
114
111
``` erlang
115
- Json = Record :to_json ().
116
112
Json = person :to_json (Record ).
117
- [ {name , <<" John" >>}, { age , 32 }] = Json .
113
+ # {name : = <<" John" >>, age : = 32 } = Json .
118
114
```
119
115
120
116
The to_json function can take a list of mutators. Mutators are applied in
@@ -123,36 +119,34 @@ the order listed except {null_is_undefined}. Supported mutators are:
123
119
* Turn undefined into null instead of skipping the field
124
120
125
121
``` erlang
126
- Record :to_json ([{null_is_undefined }]).
127
122
person :to_json (Record , [{null_is_undefined }]).
128
123
```
129
124
130
125
* Add a property
131
126
132
127
``` erlang
133
- Record :to_json ([{single , true }]).
134
128
person :to_json (Record , [{single , true }]).
129
+ person :to_json (Record , [#{single => true }).
130
+ person :to_json (Record , [#{single => true , employed => true }).
135
131
```
136
132
137
133
* Remove a property
138
134
139
135
```erlang
140
- Record :to_json ([age ]).
141
136
person :to_json (Record , [age ]).
142
137
```
143
138
144
139
* Modify based only on the json
145
140
146
141
```erlang
147
142
ModFunc = fun (Json ) ->
148
- case proplists : get_value (spouse , Json ) of
149
- undefined ->
150
- [ {single , true } | Json ];
143
+ case maps : find (spouse , Json ) of
144
+ error ->
145
+ Json # {single => true }
151
146
_ ->
152
- [ {single , false } | Json ]
147
+ Json # {single => false }
153
148
end
154
149
end .
155
- Record :to_json ([ModFunc ]).
156
150
person :to_json (Record , [ModFunc ]).
157
151
```
158
152
@@ -162,12 +156,11 @@ person:to_json(Record, [ModFunc]).
162
156
ModFunc = fun (Json , Record ) ->
163
157
case Record # person .spouse of
164
158
undefined ->
165
- [ {single , true } | Json ] ;
159
+ Json # {single => true };
166
160
_ ->
167
- [ {single , false } | Json ]
161
+ Json # {single => false }
168
162
end
169
163
end .
170
- Record :to_json ([ModFunc ]).
171
164
person :to_json (Record , [ModFunc ]).
172
165
```
173
166
@@ -176,52 +169,45 @@ person:to_json(Record, [ModFunc]).
176
169
Converting from a json structure to a record is just as simple:
177
170
178
171
```erlang
179
- {ok , Record } = person :from_json ([
180
- { <<" name" >>, <<" John" >>} ,
181
- { <<" age" >>, 32 } ,
182
- { <<" spouse" >>, null }
183
- ] ).
172
+ {ok , Record } = person :from_json (#{
173
+ <<" name" >> => <<" John" >>,
174
+ <<" age" >> => 32 ,
175
+ <<" spouse" >> => null
176
+ } ).
184
177
```
185
178
186
- It may be desireable to change 'null' into 'undefined' in the record:
179
+ It may be desirable to change 'null' into 'undefined' in the record :
187
180
188
181
```erlang
189
182
{ok , Record } = person :from_json (Json , [null_is_undefined ]).
190
183
```
191
184
192
- It may be desireable to start with an existing record instead of creating
185
+ It may be desirable to start with an existing record instead of creating
193
186
a new one :
194
187
195
188
```erlang
196
- {ok , Record2 } = Record :from_json (Json ).
197
189
{ok , Record2 } = person :from_json (Json , Record ).
198
190
{ok , Record2 } = person :from_json (Record , Json ).
199
- {ok , Record2 } = Record :from_json (Json , [null_is_undefined ]).
200
191
{ok , Record2 } = person :from_json (Record , Json , [null_is_undefined ]).
201
192
```
202
193
203
- If the json structure has a type that connot be reconciled with a type
194
+ If the json structure has a type that cannot be reconciled with a type
204
195
specified by the record definition , a list of fields with possible errors
205
196
is returned . The record will have the data that was in the json structure .
206
197
An untyped record field is the same as having the type 'any()' . There are
207
- no warings about missing properties in the json, they simply retain the
198
+ no warnings about missing properties in the json , they simply retain the
208
199
default value of the record .
209
200
210
201
```erlang
211
- {ok , Record , [age ]} = person :from_json ([ {<<" age" >>, <<" 32" >>}] ).
202
+ {ok , Record , [age ]} = person :from_json (# {<<" age" >> => <<" 32" >>}).
212
203
```
213
204
214
205
### Including in a project
215
206
216
207
If all you are using is the parse_transform , simply add rec2json as a
217
208
required application .
218
209
219
- To be able to create modules from records without the
220
- parse transform, you will need to add the rec2json script to your path in some
221
- manner. Add a call to the rec2json script during your build (in your Makefile
222
- or rebar.config precompile hook).
223
-
224
- ## Type Checking and Converstion
210
+ ## Type Checking and Conversion
225
211
226
212
Type conversion attempts to be as transparent and intuitive as possible .
227
213
There are some types that json does not represent directly , and some types
@@ -230,7 +216,7 @@ that have additional checking implemented.
230
216
Record fields that have atoms as types will have binary values in the json
231
217
checked . If the atom converted to the binary is equal to the json value , the
232
218
atom value is put into the record . When converting a record to json , atom
233
- values will be converted to binaries to conform to the erlang spec .
219
+ values will be converted to binaries .
234
220
235
221
Lists have their types checked . If there is an invalid type , the invalid
236
222
type is placed in the list , but the warning message has the index of the
@@ -242,7 +228,7 @@ invalid type placed in the warning path list. For example:
242
228
}).
243
229
244
230
type_mismatch () ->
245
- Json = [ {ids , [<<" invalid" >>, 3 ]}] ,
231
+ Json = # {ids => [<<" invalid" >>, 3 ]},
246
232
{ok , Record , Warnings } = list_holder :from_json (Json ),
247
233
# list_holder {ids = [<<" invalid" >>, 3 ]} = Record ,
248
234
[[ids , 1 ]] = Warnings .
@@ -262,7 +248,7 @@ list has the field name prepended to each. For example:
262
248
}).
263
249
264
250
type_mismatch () ->
265
- Json = [ {in_field , [ {count , <<" 0" >>}]}] ,
251
+ Json = # {in_field => # {count => <<" 0" >>}} ,
266
252
{ok , Record , Warnings } = outer :from_json (Json ),
267
253
# outer {in_field = # inner { count = <<" 0" >> } } = Record ,
268
254
[[in_filed , count ]] = Warnings .
@@ -295,9 +281,9 @@ is to convert untrusted data, such as an http post request. Converting
295
281
untrusted data into atoms can exhaust the erlang vm 's atom table, or worse
296
282
exhaust the machine' s memory . Rec2json is , by default , safe .
297
283
298
- It is still possilbe to apply a type to a record field so that json strings
284
+ It is still possible to apply a type to a record field so that json strings
299
285
will be converted to the equivalent atom using either a user defined type ,
300
- or the provied ` r2j_type:unsafe_atom() ` type.
286
+ or the provided `r2j_type :unsafe_atom ()` type .
301
287
302
288
### User defined types
303
289
@@ -313,6 +299,10 @@ For example, given the module:
313
299
- module (type_example ).
314
300
- compile ([{parse_transform , rec2json }]).
315
301
302
+ % These two lines exist to satisfy dialyzer.
303
+ - type point () :: {number (), number ()}.
304
+ - export_type ([point / 0 ]).
305
+
316
306
- record (type_example , {
317
307
some_field :: module :function (arg1 , arg2 ),
318
308
xy = {0 , 0 } :: type_example :point ()
@@ -342,4 +332,4 @@ error is thrown.
342
332
343
333
## Contributing
344
334
345
- Fork and submit a pull request with relevent tests.
335
+ Fork and submit a pull request with relevant tests .
0 commit comments