]> Pileus Git - ~andy/rhawk/blob - json.awk
Handle special JSON characters
[~andy/rhawk] / json.awk
1 # usage:
2 #       @include "json.awk"
3 #       BEGIN {
4 #               src["hello"] = "world"
5 #               json_save("test.json", src)
6 #               json_load("test.json", dst)
7 #               print dst["hello"]
8 #       }
9 #
10 # value:
11 #       object: { string : value, .. }
12 #       array:  [ value, .. ]
13 #       string: "char .."
14 #       number: (builtin)
15 #       true
16 #       false
17 #       null
18
19 # chars:
20 #       any-Unicode-character-except-"-or-\-or-control-character
21 #       \"
22 #       \\
23 #       \/
24 #       \b
25 #       \f
26 #       \n
27 #       \r
28 #       \t
29 #       \u four-hex-digits
30
31 # Helpers
32 function json_gettype(value,   key, sort, n, i)
33 {
34         if (isarray(value)) { 
35                 for (key in value)
36                         if (isarray(key))
37                                 return "error"
38                 n = asorti(value, sort)
39                 for (i = 0; i < n; i++)
40                         if (sort[i+1] != i)
41                                 return "object"
42                 return "array"
43         } else {
44                 if (value == 0 && value == "")
45                         return "null"
46                 if (value == value + 0)
47                         return "number"
48                 if (value == value "")
49                         return "string"
50                 return "error"
51         }
52 }
53
54 function json_join(array, sep,   i, str)
55 {
56         str = array[0]
57         for (i = 1; i < length(array); i++)
58                 str = str sep array[i]
59         return str
60 }
61
62 function json_copy(dst, to, src,   key)
63 {
64         if (isarray(src)) {
65                 delete dst[to]
66                 for (key in src) {
67                         dst[to][key]
68                         json_copy(dst[to], key, src[key])
69                 }
70         } else {
71                 dst[to] = src
72         }
73 }
74
75
76 # Write functions
77 function json_write_value(value, pad)
78 {
79         switch (json_gettype(value)) {
80                 case "object": return json_write_object(value, pad)
81                 case "array":  return json_write_array(value, pad)
82                 case "number": return json_write_number(value)
83                 case "string": return json_write_string(value)
84                 case "null":   return "null"
85                 default:       return "error"
86         }
87 }
88
89 function json_write_object(object, pad,   n, i, sort, key, val, data, len, max)
90 {
91         n = asorti(object, sort)
92         for (i = 0; i < n; i++) {
93                 key = json_write_string(sort[i+1])
94                 if (length(key) > max)
95                         max = length(key)
96         }
97         for (i = 0; i < n; i++) {
98                 key = json_write_string(sort[i+1])
99                 val = json_write_value(object[sort[i+1]],
100                         sprintf("%s  %"max"s  ", pad, ""))
101                 data[i] = sprintf("%-"(max+1)"s %s", key":", val)
102         }
103         return "{ " json_join(data, ",\n  " pad) " }"
104 }
105
106 function json_write_array(array, pad,   i, data)
107 {
108         for (i = 0; i < length(array); i++)
109                 data[i] = json_write_value(array[i], pad "  ")
110         return "[ " json_join(data, ",\n  " pad) " ]"
111 }
112
113 function json_write_number(number)
114 {
115         return "" number ""
116 }
117
118 function json_write_string(string)
119 {
120         gsub(/\\/, "\\\\", string)
121         gsub(/"/,  "\\\"", string)
122         gsub(/\b/, "\\b",  string)
123         gsub(/\f/, "\\f",  string)
124         gsub(/\n/, "\\n",  string)
125         gsub(/\r/, "\\r",  string)
126         gsub(/\t/, "\\t",  string)
127
128         return "\"" string "\""
129 }
130
131
132 # Read functions
133 function json_tokenize(str, tokens,   i, line, items, table, type, found)
134 {
135         table["term"]  = "^[\\[\\]{}:,]"
136         table["str"]   = "^\"([^\\\\\"]|\\\\.)*\""
137         table["num"]   = "^[+-]?[0-9]+(.[0-9]+)?"
138         table["var"]   = "^(true|false|null)"
139         table["space"] = "^[ \\t]+"
140         table["line"]  = "^\n"
141         i = 0;
142         line = 1;
143         while (length(str) > 0) {
144                 found = 0
145                 for (type in table) {
146                         #print "match: str=["str"] type="type" regex=/"table[type]"/"
147                         if (match(str, table[type], items) > 0) {
148                                 #print "       len="RLENGTH" item=["items[0]"]"
149                                 if (type == "line")
150                                         line++;
151                                 if (type == "term")
152                                         type = items[0]
153                                 if (type != "space" && type != "line") {
154                                         tokens[i]["line"] = line
155                                         tokens[i]["type"] = type
156                                         tokens[i]["text"] = items[0]
157                                         i++
158                                 }
159                                 str = substr(str, RLENGTH+1)
160                                 found = 1
161                                 break
162                         }
163                 }
164                 if (!found) {
165                         debug("line " line ": error tokenizing")
166                         return 0
167                 }
168         }
169
170         #for (i = 0; i < length(tokens); i++)
171         #       printf "%-3s %-5s [%s]\n", i":",
172         #               tokens[i]["type"], tokens[i]["text"]
173
174         return i
175 }
176
177 function json_parse_value(tokens, i, value, key,   line, type, text)
178 {
179         if (!i    ) i = 0;
180         if (!depth) depth = 0
181         depth++
182         line = tokens[i]["line"]
183         type = tokens[i]["type"]
184         text = tokens[i]["text"]
185         #printf "parse %d: i=%-2d type=%-3s text=%s\n", depth, i, type, text
186         switch (type) {
187                 case "{":   i = json_parse_object(tokens, i, value, key); break
188                 case "[":   i = json_parse_array(tokens,  i, value, key); break
189                 case "str": i = json_parse_string(tokens, i, value, key); break
190                 case "num": i = json_parse_number(tokens, i, value, key); break
191                 case "var": i = json_parse_var(tokens,    i, value, key); break
192                 default:    debug("line "line": error type="type" text="text); return 0
193         }
194         depth--
195         return i
196 }
197
198 function json_parse_object(tokens, i, value, key,   object, k, v)
199 {
200         if (tokens[i++]["text"] != "{")
201                 return 0;
202
203         if (tokens[i]["text"] != "}") {
204                 do {
205                         delete k
206                         delete v
207                         if (!(i=json_parse_value(tokens, i, k, 0)))
208                                 return 0
209                         if (tokens[i++]["text"] != ":")
210                                 return 0
211                         if (!(i=json_parse_value(tokens, i, v, 0)))
212                                 return 0
213                         json_copy(object, k[0], v[0]) 
214                 } while (tokens[i++]["text"] == ",")
215                 i--
216         }
217
218         if (tokens[i++]["text"] != "}")
219                 return 0;
220
221         json_copy(value, key, object)
222         return i
223 }
224
225 function json_parse_array(tokens, i, value, key,   array, k, v)
226 {
227         if (tokens[i++]["text"] != "[")
228                 return 0;
229
230         if (tokens[i]["text"] != "]") {
231                 do {
232                         delete v
233                         if (!(i=json_parse_value(tokens, i, v, 0)))
234                                 return 0
235                         json_copy(array, k++, v[0]) 
236                 } while (tokens[i++]["text"] == ",")
237                 i--
238         }
239
240         if (tokens[i++]["text"] != "]")
241                 return 0;
242
243         json_copy(value, key, array)
244         return i
245 }
246
247 function json_parse_number(tokens, i, value, key,   text)
248 {
249         text = tokens[i++]["text"]
250         json_copy(value, key, text + 0)
251         #print "parse_number: " (text + 0)
252         return i
253 }
254
255 function json_parse_string(tokens, i, value, key,   text)
256 {
257         text = tokens[i++]["text"]
258         len  = length(text);
259         text = len == 2 ? "" : substr(text, 2, len-2)
260
261         gsub(/\\\\/, "\\", text)
262         gsub(/\\"/,  "\"", text)
263         gsub(/\\b/,  "\b", text)
264         gsub(/\\f/,  "\f", text)
265         gsub(/\\n/,  "\n", text)
266         gsub(/\\r/,  "\r", text)
267         gsub(/\\t/,  "\t", text)
268
269         json_copy(value, key, text)
270         #print "parse_string: [" text "]"
271         return i
272 }
273
274 function json_parse_var(tokens, i, value, key,   text, null)
275 {
276         switch (tokens[i++]["text"]) {
277                 case "true":  json_copy(value, key, 1==1); break;
278                 case "false": json_copy(value, key, 1==2); break;
279                 case "null":  json_copy(value, key, null); break;
280         }
281         #print "parse_var: " text " -> " text == "true"
282         return i
283 }
284
285
286 # Nice API?
287 function json_load(file, var,   line, text, tokens, data, key)
288 {
289         while ((getline line < file) > 0)
290                 text = text line "\n"
291         close(file)
292         if (!json_tokenize(text, tokens))
293                 return ""
294         if (!json_parse_value(tokens, 0, data, 0))
295                 return ""
296         if (!isarray(data[0]))
297                 return data[0]
298         for (key in data[0])
299                 json_copy(var, key, data[0][key])
300         return 1
301 }
302
303 function json_save(file, var,   cmd, tmp)
304 {
305         cmd = "mktemp " file ".XXX"
306         cmd | getline tmp
307         close(cmd)
308         print json_write_value(var) > tmp
309         close(tmp)
310         system("mv " tmp " " file)
311 }
312
313
314 # Test functions
315 function json_test_write()
316 {
317         num      = 42
318         str      = "hello, world"
319         arr[0]   = "zero"
320         arr[1]   = "one"
321         arr[2]   = "two"
322         obj["A"] = "a!"
323         obj["B"] = "b!"
324         obj["C"] = "c!"
325         json_copy(mix, "number", num);
326         json_copy(mix, "str",    str);
327         json_copy(mix, "arr",    arr);
328         json_copy(mix, "obj",    obj);
329         json_copy(dub, "first",  mix);
330         json_copy(dub, "second", mix);
331         print json_write_value(num)
332         print json_write_value(str)
333         print json_write_value(arr)
334         print json_write_value(obj)
335         print json_write_value(mix)
336         print json_write_value(dub)
337 }
338
339 function json_test_read()
340 {
341         json_tokenize("[8, \"abc\", 9]", tokens)
342         json_parse_value(tokens, 0, array, 0)
343         print json_write_value(array[0])
344
345         json_tokenize("{\"abc\": 1, \"def\": 2}", tokens)
346         json_parse_value(tokens, 0, array, 0)
347         print json_write_value(array[0])
348
349         json = "{ \"first\":  { \"arr\":    [ \"\",             \n" \
350                "                              -1,               \n" \
351                "                              1.2,              \n" \
352                "                              true,             \n" \
353                "                              false,            \n" \
354                "                              null    ],        \n" \
355                "                \"number\": 42,                 \n" \
356                "                \"eobj\":   [ ],                \n" \
357                "                \"earr\":   { },                \n" \
358                "                \"obj\":    { \"A\": \"a!\",    \n" \
359                "                              \"B\": \"b!\",    \n" \
360                "                              \"C\": \"c!\" },  \n" \
361                "                \"str\":    \"hello, world\" }, \n" \
362                "  \"second\": { \"arr\":    [ \"zero\",         \n" \
363                "                              \"one\",          \n" \
364                "                              \"two\",          \n" \
365                "                              \"\\\"\t\r\b\" ], \n" \
366                "                \"number\": 42,                 \n" \
367                "                \"obj\":    { \"A\": \"a!\",    \n" \
368                "                              \"B\": \"b!\",    \n" \
369                "                              \"C\": \"c!\" },  \n" \
370                "                \"str\":    \"hello, world\" } }\n"
371
372         json_tokenize(json, tokens)
373         json_parse_value(tokens, 0, array, 0)
374         print json_write_value(array[0])
375 }
376
377 function json_test_files()
378 {
379         print "load: [" json_load("email.txt", mail) "]"
380         print "mail: "  json_write_value(mail, "      ")
381         mail["andy753421"] = "andy753421@gmail.com"
382         mail["andy"]       = "andy@gmail.com"
383         mail["somebody"]   = "foo@example.com"
384         print "mail: "  json_write_value(mail, "      ")
385         print "save: [" json_save("email.txt", mail) "]"
386 }
387
388 # Main
389 BEGIN {
390         #json_test_write()
391         #json_test_read()
392         #json_test_files()
393 }