# usage: # @include "json.awk" # BEGIN { # src["hello"] = "world" # json_save("test.json", src) # json_load("test.json", dst) # print dst["hello"] # } # # value: # object: { string : value, .. } # array: [ value, .. ] # string: "char .." # number: (builtin) # true # false # null # # chars: # any-Unicode-character-except-"-or-\-or-control-character # \" # \\ # \/ # \b # \f # \n # \r # \t # \u four-hex-digits # Helpers function json_gettype(value, key, sort, n, i) { if (isarray(value)) { for (key in value) if (isarray(key)) return "error" n = asorti(value, sort) for (i = 0; i < n; i++) if (sort[i+1] != i) return "object" return "array" } else { if (value == 0 && value == "") return "null" if (value == value + 0) return "number" if (value == value "") return "string" return "error" } } function json_join(array, sep, i, str) { str = array[0] for (i = 1; i < length(array); i++) str = str sep array[i] return str } function json_copy(dst, to, src, key) { if (isarray(src)) { delete dst[to] for (key in src) { dst[to][key] json_copy(dst[to], key, src[key]) } } else { dst[to] = src } } # Write functions function json_write_value(value, pad) { switch (json_gettype(value)) { case "object": return json_write_object(value, pad) case "array": return json_write_array(value, pad) case "number": return json_write_number(value) case "string": return json_write_string(value) case "null": return "null" default: return "error" } } function json_write_object(object, pad, n, i, sort, key, val, data, len, max) { n = asorti(object, sort) for (i = 0; i < n; i++) { key = json_write_string(sort[i+1]) if (length(key) > max) max = length(key) } for (i = 0; i < n; i++) { key = json_write_string(sort[i+1]) val = json_write_value(object[sort[i+1]], sprintf("%s %"max"s ", pad, "")) data[i] = sprintf("%-"(max+1)"s %s", key":", val) } return "{ " json_join(data, ",\n " pad) " }" } function json_write_array(array, pad, i, data) { for (i = 0; i < length(array); i++) data[i] = json_write_value(array[i], pad " ") return "[ " json_join(data, ",\n " pad) " ]" } function json_write_number(number) { return "" number "" } function json_write_string(string) { gsub(/\\/, "\\\\", string) gsub(/"/, "\\\"", string) gsub(/\b/, "\\b", string) gsub(/\f/, "\\f", string) gsub(/\n/, "\\n", string) gsub(/\r/, "\\r", string) gsub(/\t/, "\\t", string) return "\"" string "\"" } # Read functions function json_tokenize(str, tokens, i, line, items, table, type, found) { table["term"] = "^[\\[\\]{}:,]" table["str"] = "^\"([^\\\\\"]|\\\\.)*\"" table["num"] = "^[+-]?[0-9]+(.[0-9]+)?" table["var"] = "^(true|false|null)" table["space"] = "^[ \\t]+" table["line"] = "^\n" i = 0; line = 1; while (length(str) > 0) { found = 0 for (type in table) { #print "match: str=["str"] type="type" regex=/"table[type]"/" if (match(str, table[type], items) > 0) { #print " len="RLENGTH" item=["items[0]"]" if (type == "line") line++; if (type == "term") type = items[0] if (type != "space" && type != "line") { tokens[i]["line"] = line tokens[i]["type"] = type tokens[i]["text"] = items[0] i++ } str = substr(str, RLENGTH+1) found = 1 break } } if (!found) { debug("line " line ": error tokenizing") return 0 } } #for (i = 0; i < length(tokens); i++) # printf "%-3s %-5s [%s]\n", i":", # tokens[i]["type"], tokens[i]["text"] return i } function json_parse_value(tokens, i, value, key, line, type, text) { if (!i ) i = 0; if (!depth) depth = 0 depth++ line = tokens[i]["line"] type = tokens[i]["type"] text = tokens[i]["text"] #printf "parse %d: i=%-2d type=%-3s text=%s\n", depth, i, type, text switch (type) { case "{": i = json_parse_object(tokens, i, value, key); break case "[": i = json_parse_array(tokens, i, value, key); break case "str": i = json_parse_string(tokens, i, value, key); break case "num": i = json_parse_number(tokens, i, value, key); break case "var": i = json_parse_var(tokens, i, value, key); break default: debug("line "line": error type="type" text="text); return 0 } depth-- return i } function json_parse_object(tokens, i, value, key, object, k, v) { if (tokens[i++]["text"] != "{") return 0; if (tokens[i]["text"] != "}") { do { delete k delete v if (!(i=json_parse_value(tokens, i, k, 0))) return 0 if (tokens[i++]["text"] != ":") return 0 if (!(i=json_parse_value(tokens, i, v, 0))) return 0 json_copy(object, k[0], v[0]) } while (tokens[i++]["text"] == ",") i-- } if (tokens[i++]["text"] != "}") return 0; json_copy(value, key, object) return i } function json_parse_array(tokens, i, value, key, array, k, v) { if (tokens[i++]["text"] != "[") return 0; if (tokens[i]["text"] != "]") { do { delete v if (!(i=json_parse_value(tokens, i, v, 0))) return 0 json_copy(array, k++, v[0]) } while (tokens[i++]["text"] == ",") i-- } if (tokens[i++]["text"] != "]") return 0; json_copy(value, key, array) return i } function json_parse_number(tokens, i, value, key, text) { text = tokens[i++]["text"] json_copy(value, key, text + 0) #print "parse_number: " (text + 0) return i } function json_parse_string(tokens, i, value, key, text) { text = tokens[i++]["text"] len = length(text); text = len == 2 ? "" : substr(text, 2, len-2) gsub(/\\\\/, "\\", text) gsub(/\\"/, "\"", text) gsub(/\\b/, "\b", text) gsub(/\\f/, "\f", text) gsub(/\\n/, "\n", text) gsub(/\\r/, "\r", text) gsub(/\\t/, "\t", text) json_copy(value, key, text) #print "parse_string: [" text "]" return i } function json_parse_var(tokens, i, value, key, text, null) { switch (tokens[i++]["text"]) { case "true": json_copy(value, key, 1==1); break; case "false": json_copy(value, key, 1==2); break; case "null": json_copy(value, key, null); break; } #print "parse_var: " text " -> " text == "true" return i } # String API function json_decode(string, var, tokens, data, key) { if (!json_tokenize(string, tokens)) return "" if (!json_parse_value(tokens, 0, data, 0)) return "" if (!isarray(data[0])) return data[0] for (key in data[0]) json_copy(var, key, data[0][key]) return 1 } function json_encode(var, pad) { return json_write_value(var, pad) } # File API function json_load(file, var, line, string) { delete var while ((getline line < file) > 0) string = string line "\n" return json_decode(string, var) } function json_save(file, var, cmd, tmp) { cmd = "mktemp " file ".XXX" cmd | getline tmp close(cmd) print json_encode(var) > tmp close(tmp) system("mv " tmp " " file) } # Test functions function json_test_write() { print "Testing JSON Write:" num = 42 str = "hello, world" arr[0] = "zero" arr[1] = "one" arr[2] = "two" obj["A"] = "a!" obj["B"] = "b!" obj["C"] = "c!" json_copy(mix, "number", num); json_copy(mix, "str", str); json_copy(mix, "arr", arr); json_copy(mix, "obj", obj); json_copy(dub, "first", mix); json_copy(dub, "second", mix); print json_encode(num) print json_encode(str) print json_encode(arr) print json_encode(obj) print json_encode(mix) print json_encode(dub) } function json_test_read() { print "Testing JSON Read:" json_decode("[8, \"abc\", 9]", array); print json_encode(array) json_decode("{\"abc\": 1, \"def\": 2}", array) print json_encode(array) json = "{ \"first\": { \"arr\": [ \"\", \n" \ " -1, \n" \ " 1.2, \n" \ " true, \n" \ " false, \n" \ " null ], \n" \ " \"number\": 42, \n" \ " \"eobj\": [ ], \n" \ " \"earr\": { }, \n" \ " \"obj\": { \"A\": \"a!\", \n" \ " \"B\": \"b!\", \n" \ " \"C\": \"c!\" }, \n" \ " \"str\": \"hello, world\" }, \n" \ " \"second\": { \"arr\": [ \"zero\", \n" \ " \"one\", \n" \ " \"two\", \n" \ " \"\\\"\t\r\b\" ], \n" \ " \"number\": 42, \n" \ " \"obj\": { \"A\": \"a!\", \n" \ " \"B\": \"b!\", \n" \ " \"C\": \"c!\" }, \n" \ " \"str\": \"hello, world\" } }\n" json_decode(json, array) print json_encode(array) } function json_test_files() { print "Testing JSON Files:" print "load: [" json_load("email.txt", mail) "]" print "mail: " json_encode(mail, " ") mail["andy753421"] = "andy753421@gmail.com" mail["andy"] = "andy@gmail.com" mail["somebody"] = "foo@example.com" print "mail: " json_encode(mail, " ") print "save: [" json_save("email.txt", mail) "]" } # Main BEGIN { #json_test_write() #json_test_read() #json_test_files() }