├── LICENSE.md
├── README.md
└── json.mac
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, David Noël (of Black Chair Studios, Inc.) and Blake McArthur (of BMTS Intranet, Inc.)
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of Black Chair Studios, Inc., BMTS Intranet, Inc., nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | netdata-json
2 | ============
3 |
4 | A fun little library for relating Net.Data tables to JSON objects. Facilitates the use of front-end frameworks like Backbone.js or Ember.js with a Net.Data back end. Usage is documented in-line, to some extent.
5 |
6 | This project was built as a collaboration between [Black Chair Studios, Inc.](http://www.blackchair.net) and [BMTS Intranet, Inc.](http://bmtsintranet.com)
7 |
--------------------------------------------------------------------------------
/json.mac:
--------------------------------------------------------------------------------
1 | %{
2 | ********* JSON tools for Net.Data ***********
3 | This Net.Data JSON parser operates on a subset
4 | of JSON. It only works with JSON structures of
5 | the form {...} or [{...},{...},...,{...}] and
6 | builds a table of corresponding values. The
7 | JSON strings will ultimately be passed in as
8 | (URL encoded) GET or POST variables.
9 |
10 | The to_json function creates a JSON string of
11 | the above form based on a given table.
12 | %}
13 |
14 | %DEFINE {
15 | %{
16 | This is the table that will hold the
17 | parsed data.
18 | %}
19 | json_table = %TABLE
20 |
21 | %{
22 | A simple JSON string for testing.
23 | %}
24 | test_json = {{"first_name":"David","last_name":"Noel"}%}
25 | test_json2 = {[{"first_name":"Blake","last_name":"McArthur"},{"first_name":"David","last_name":"Noel"}]%}
26 | test_json3 = {
27 | [{"first_name" : "Blake",
28 | "last_name" : "McArthur"},
29 | {"first_name" : "David",
30 | "last_name" : "Noell"}
31 | ]
32 | %}
33 | %}
34 |
35 | %FUNCTION(DTW_REXX) parse_json (IN json, INOUT table) {
36 | /*
37 | parse_stack will be used as a makeshift
38 | "stack" for keeping track of parse
39 | position in nested structures.
40 | Basically, each time a new level of the
41 | structure is encountered, a new letter
42 | will be added to this string, and then
43 | removed when the end token is found.
44 | "o" denotes an object, "a" an array,
45 | and "s" a string. "k" and "v" are used
46 | to denote whether the item is a key or
47 | a value.
48 | */
49 | parse_stack = ""
50 |
51 | /*
52 | Possible states are as follows (see the
53 | code in their respective sections of the
54 | parse loop for a better understanding).
55 |
56 | "go" : Opening state. Walks through
57 | whitespace until { or [.
58 | "key" : Look for " to start collecting
59 | key name in "str" state.
60 | "endkey" : "str" exits here when
61 | dealing with a key.
62 | This is where table
63 | columns can be defined.
64 | "val" : Look for opening character to
65 | decide which state to collect
66 | value in (obj, arr, or str), if
67 | any (true, false, and null are
68 | extracted in this state).
69 | "endval" : After value has been
70 | collected, this state
71 | becomes active. This is
72 | where row values can
73 | be set.
74 | "str" : Run through "string" type
75 | tokens, ignoring escaped
76 | sequences.
77 | "obj" : Run through an object or a
78 | series of nested objects.
79 | "arr" : Run through an array or a series
80 | of nested arrays.
81 | "done" : Final state.
82 | "error" : Exit.
83 | */
84 | state = "go"
85 |
86 | /*
87 | collecting is a boolean for knowing
88 | when to append characters to collector,
89 | which stores items for extraction from
90 | the JSON object.
91 | */
92 | collecting = 0
93 | collector = ""
94 |
95 | /*
96 | These counters keep track of position
97 | in the table.
98 | */
99 | column = 1
100 | row = 1
101 |
102 | do while json <> '' /* iterate through json string until it is empty */
103 | parse var json 1 char 2 json /* pull the first character from the json string */
104 |
105 |
106 | if collecting = 1 then
107 | collector = collector || char
108 |
109 | select
110 |
111 | when state = "go" then do
112 | column = 1
113 | if char = '[' & parse_stack = '' then
114 | parse_stack = parse_stack || "a"
115 | else if char = '{' then do
116 | parse_stack = parse_stack || "o"
117 | state = "key"
118 | end
119 | else if char = '' | char = x2c('15') then /* whitespace matches null value */
120 | nop
121 | else do
122 | state = "error"
123 | say 'error at 1 with char =#'c2x(char)'#
'
124 | end
125 | end
126 |
127 | when state = "key" then do
128 | if char = '"' then do
129 | parse_stack = parse_stack || "ks"
130 | collector = char
131 | collecting = 1
132 | state = "str"
133 | end
134 | else if char = '' | char = x2c('15') then /* whitespace matches null value */
135 | nop
136 | else do
137 | say 'error at 2
'
138 | state = "error"
139 | end
140 | end
141 |
142 | when state = "endkey" then do
143 | collecting = 0
144 | if char = ":" & Right(parse_stack,1) = "k" then do
145 | parse_stack = Delstr(parse_stack, Length(parse_stack) )
146 | parse var collector '"' key '"'
147 | table_N.column = key
148 | collector = ""
149 | state = "val"
150 | end
151 | else if char = '' | char = x2c('15') then /* whitespace matches null value */
152 | nop
153 | else do
154 | say 'error at 3
'
155 | state = "error"
156 | end
157 | end
158 |
159 | when state = "val" then do
160 | if char = '"' then do
161 | parse_stack = parse_stack || "vs"
162 | collector = char
163 | collecting = 1
164 | state = "str"
165 | end
166 |
167 | else if char = '{' then do
168 | parse_stack = parse_stack || "vo"
169 | collector = char
170 | collecting = 1
171 | state = "obj"
172 | end
173 |
174 | else if char = '[' then do
175 | parse_stack = parse_stack || "va"
176 | collector = char
177 | collecting = 1
178 | state = "arr"
179 | end
180 |
181 | else if char = 't' | char = 'f' | char = 'n' | char = '-' | (char <= 9 & char >= 0) then do
182 | parse_stack = parse_stack || "v"
183 | collector = char
184 | collecting = 1
185 | state = "endval"
186 | end
187 |
188 | else if char = '' | char = x2c('15') then do /* whitespace matches null value */
189 | nop
190 | end
191 | else do
192 | say 'error at 4
'
193 | state = "error"
194 | end
195 | end
196 |
197 | when state = "endval" then do
198 |
199 | if Right(parse_stack,1) <> "v" then do
200 | say 'error at 5
'
201 | state = "error"
202 | end
203 | else if char = ',' then do
204 | parse_stack = Delstr(parse_stack, Length(parse_stack) )
205 | table_V.row.column = Strip(Delstr(collector, Length(collector) ))
206 | column = column + 1
207 | collecting = 0
208 | collector = ""
209 | state = "key"
210 | end
211 | else if char = '}' then do
212 | parse_stack = Delstr(parse_stack, Length(parse_stack) )
213 | table_V.row.column = Strip(Delstr(collector, Length(collector) ))
214 | collecting = 0
215 | collector = ""
216 |
217 | /* now end object and check for more */
218 | if Right(parse_stack,1) = "o" then do
219 | parse_stack = Delstr(parse_stack, Length(parse_stack) )
220 | if parse_stack = '' then do
221 | state = "done"
222 | end
223 | else if parse_stack = "a" then do /* level 4 */
224 | do while state = "endval"
225 | parse var json 1 char 2 json
226 | if char = "]" then
227 | state = "done"
228 | else if char = "," then
229 | state = "go"
230 | else if char = '' | char = x2c('15') then
231 | nop
232 | else do
233 | say 'error at 6 with char=#'char'# and parse_stack='parse_stack'
'
234 | state = "error"
235 | end
236 | end
237 | row = row + 1
238 | end /*level 4 */
239 | else do
240 | say 'error at 7
'
241 | state = "error"
242 | end
243 | end /* end "if R ight" */
244 | else do
245 | say 'error at 8
'
246 | state = "error"
247 | end
248 | end /* end "if }" */
249 | else
250 | nop
251 | /* there's intentionally no error fallthrough here,
252 | because it needs to walk through non-string, non-
253 | whitespace tokens like null, numbers, etc */
254 | end /* end "when" */
255 |
256 | when state = "str" then do
257 | if char = "\" then do
258 | parse var json 1 char 2 json
259 | collector = collector || char
260 | end
261 | else if char = '"' then do
262 | parse_stack = Delstr(parse_stack, Length(parse_stack) )
263 | mode = Right(parse_stack,1)
264 | if mode = "k" then
265 | state = "endkey"
266 | else if mode = "v" then
267 | state = "endval"
268 | else if mode = "o" then
269 | state = "obj"
270 | else if mode = "a" then
271 | state = "arr"
272 | end
273 | else
274 | nop
275 | end
276 |
277 | when state = "obj" then do
278 | if char = "{" then
279 | parse_stack = parse_stack || "o"
280 | else if char = '"' then do
281 | parse_stack = parse_stack || "s"
282 | state = "str"
283 | end
284 | else if char = "}" then do
285 | parse_stack = Delstr(parse_stack, Length(parse_stack) )
286 | mode = Right(parse_stack,1)
287 | if mode = "v" then
288 | state = "endval"
289 | end
290 | end
291 |
292 | when state = "arr" then do
293 | if char = "[" then
294 | parse_stack = parse_stack || "a"
295 | else if char = '"' then do
296 | parse_stack = parse_stack || "s"
297 | state = "str"
298 | end
299 | else if char = "]" then do
300 | parse_stack = Delstr(parse_stack, Length(parse_stack) )
301 | mode = Right(parse_stack,1)
302 | if mode = "v" then
303 | state = "endval"
304 | end
305 | end
306 |
307 | when state = "done" then do
308 | json = ''
309 | end
310 |
311 | otherwise do
312 | say 'Houston, we have a problem - state is 'state'
'
313 | state = "error"
314 | /* print error message here if desired */
315 | say 'We encountered a problem
'
316 | return -1
317 | end
318 |
319 | end /* end select */
320 | end /* end do while loop */
321 | table_COLS=column /* need to set number of columns and rows, else we get 1007 error with dtw_tb_table */
322 | table_ROWS=row /* these final values for column and row position should match the table dimensions */
323 | return 0 /* exit function */
324 | %report{%}
325 | %}
326 |
327 | %FUNCTION(DTW_REXX) to_json (IN table) {
328 | json = '['
329 |
330 | do i = 1 to table_ROWS-1
331 |
332 | if i = 1 then
333 | json = json'{'
334 | else
335 | json = json',{'
336 |
337 | do j = 1 to table_COLS
338 | if j <> 1 then
339 | json = json','
340 |
341 | nn = table_N.j
342 |
343 | vv = table_V.i.j
344 |
345 |
346 | json = json'"'nn'":'vv
347 |
348 | end
349 |
350 | json = json'}'
351 |
352 | end
353 |
354 | json = json']'
355 |
356 | say json
357 |
358 | return 0 /* exit function */
359 | %report{%}
360 | %}
361 |
362 |
363 |
364 |
365 | %{ ***************TEST**************** %}
366 | %HTML(REPORT) {
367 |
368 |
test_json=$(test_json)
371 | 372 | @parse_json(test_json,json_table) 373 | @DTW_TB_TABLE(json_table) 374 |test_json2=$(test_json2)
376 | 377 | @parse_json(test_json2,json_table) 378 | @DTW_TB_TABLE(json_table) 379 | 380 |test_json3=$(test_json3)
381 | @parse_json(test_json3,json_table) 382 | @DTW_TB_TABLE(json_table) 383 | 384 |