├── 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 |

$(DTW_MACRO_FILENAME)

369 | 370 |

test_json=$(test_json)

371 | 372 | @parse_json(test_json,json_table) 373 | @DTW_TB_TABLE(json_table) 374 |
375 |

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 |
385 | @to_json(json_table) 386 | %} 387 | --------------------------------------------------------------------------------