/* json beautifier default: print readable json -p: print each string/number/boolean/null value prefixed with a hierarchical path of object/array labels -u: transform the above format back into json */ #include #include #include void* gimme(void *p, ulong n){ p = realloc(p, n); if(p == nil) sysfatal("realloc: %r"); setrealloctag(p, getcallerpc(&n)); return p; } void cout(int c){ static char b[7654]; static int n; if(c == 0 || n == sizeof b){ write(1, b, n); n = 0; } b[n++] = c; } void sout(char *s){ while(*s) cout(*s++); } void cpath(int c){ static char *s; static int u, a; if(c == -1){ while(u && s[--u] != '[' && s[u] != '/') ; return; } if(c == '/' && u == 0) return; if(u == a) s = gimme(s, a += 234); s[u++] = c; if(c == 0) sout(s); } void number(void (*putc)(int), double n){ char b[25]; int i; i = n; if(i == n) seprint(b, b + sizeof b, "%d", i); else seprint(b, b + sizeof b, "%g", n); for(i = 0; b[i]; i++) putc(b[i]); } void string(void (*putc)(int), char *s){ for(; *s; s++) switch(*s){ case '"': putc('\\'); putc('"'); break; case '\\': putc('\\'); putc('\\'); break; case '\b': putc('\\'); putc('b'); break; case '\t': putc('\\'); putc('t'); break; case '\f': putc('\\'); putc('f'); break; case '\n': putc('\\'); putc('n'); break; case '\r': putc('\\'); putc('r'); break; case '/': case '[': case ']': case ':': if(putc == cpath) goto hex; default: if(*s & 0xe0) putc(*s); else{ hex: putc('\\'); putc('u'); putc('0'); putc('0'); putc('0' + (*s >> 4)); putc((*s & 15) > 9 ? 'A' - 10 + (*s & 15) : '0' + (*s & 15)); } break; } } void index(int i){ int m; cpath('['); for(m = 10; i / m; m *= 10) ; while(m > 1){ m /= 10; cpath('0' + i / m); i %= m; } cpath(']'); } void pretty(JSON *j, int d){ JSONEl *e; int i; switch(j->t){ case JSONNull: sout("null"); break; case JSONBool: sout(j->n == 0 ? "false" : "true"); break; case JSONNumber: number(cout, j->n); break; case JSONString: cout('"'); string(cout, j->s); cout('"'); break; case JSONArray: cout('['); for(e = j->first; e != nil; e = e->next){ cout('\n'); for(i = 0; i < d; i++) cout('\t'); pretty(e->val, d + 1); if(e->next != nil) cout(','); } cout('\n'); for(i = 1; i < d; i++) cout('\t'); cout(']'); break; case JSONObject: cout('{'); for(e = j->first; e != nil; e = e->next){ cout('\n'); for(i = 0; i < d; i++) cout('\t'); cout('"'); string(cout, e->name); cout('"'); cout(':'); cout(' '); pretty(e->val, d + 1); if(e->next != nil) cout(','); } cout('\n'); for(i = 1; i < d; i++) cout('\t'); cout('}'); break; } } void path(JSON *j){ JSONEl *e; int i; switch(j->t){ case JSONNull: cpath(0); sout(": null\n"); break; case JSONBool: cpath(0); sout(j->n == 0 ? ": false\n" : ": true\n"); break; case JSONNumber: cpath(0); cout(':'); cout(' '); number(cout, j->n); cout('\n'); break; case JSONString: cpath(0); cout(':'); cout(' '); string(cout, j->s); cout('\n'); break; case JSONArray: for(i = 0, e = j->first; e != nil; e = e->next){ index(i++); path(e->val); cpath(-1); } break; case JSONObject: for(e = j->first; e != nil; e = e->next){ cpath('/'); string(cpath, e->name); path(e->val); cpath(-1); } break; } } Rune rune(char *s){ Rune r; int i; for(r = i = 0; i < 4; i++) if(*s >= '0' && *s <= '9') r = r << 4 | *s++ - '0'; else if(*s >= 'A' && *s <= 'F') r = r << 4 | *s++ - 'A' + 10; else if(*s >= 'a' && *s <= 'f') r = r << 4 | *s++ - 'a' + 10; else return Runeerror; return r; } char* unstring(char *s, char *e){ char *ns, *ne; Rune r; if(s == nil) return nil; ne = ns = gimme(nil, e - s + 1); while(s < e){ if(*s == '\\' && s + 1 < e) switch(s[1]){ case '\\': *ne++ = '\\'; s += 2; continue; case '"': *ne++ = '"'; s += 2; continue; case 'b': *ne++ = '\b'; s += 2; continue; case 't': *ne++ = '\t'; s += 2; continue; case 'f': *ne++ = '\f'; s += 2; continue; case 'n': *ne++ = '\n'; s += 2; continue; case 'r': *ne++ = '\r'; s += 2; continue; case 'u': if(e - s < 6 || (r = rune(s + 2)) == Runeerror) break; s += 6; ne += runetochar(ne, &r); continue; } *ne++ = *s++; } *ne = 0; return ne - ns < e - s ? gimme(ns, ne - ns + 1) : ns; } JSONEl* new(char *s){ JSONEl *e; e = gimme(nil, sizeof *e); e->name = s; e->val = gimme(nil, sizeof(JSON)); e->val->t = JSONNull; e->next = nil; return e; } void usage(void){ sysfatal("usage: %s [-p | -u] < data.json", argv0); } void main(int argc, char **argv){ JSON *r, *j; JSONEl **p; int a, u, n; char *s, *t, m; m = 0; ARGBEGIN{ default: usage(); case 'u': if(m) usage(); m = 'u'; break; case 'p': if(m) usage(); m = 'p'; break; }ARGEND if(argc) usage(); s = nil; u = 0; a = 1999; while(9){ if(a - u < 2000) s = gimme(s, a <<= 1); n = read(0, s + u, a - u); if(n < 1) break; u += n; } if(n) sysfatal("read: %r"); s = gimme(s, u + 1); s[u] = 0; #define err(msg) sysfatal("%s near #%d (byte %d)", msg, utfnlen(s, a), a); if(m == 'u'){ r = gimme(nil, sizeof *r); r->t = JSONNull; a = 0; while(9){ while(s[a] == '\n') a++; if(s[a] == 0) break; j = r; while(s[a] && s[a] != ':' && s[a] != '\n'){ if(j->t == JSONNull){ j->t = s[a] == '[' ? JSONArray : JSONObject; j->first = nil; } if(s[a] == '['){ if(j->t != JSONArray) err("not an array"); n = 0; while(s[++a] >= '0' && s[a] <= '9') n = n * 10 + s[a] - '0'; if(s[a++] != ']') err("bad array index"); for(p = &j->first; n--; p = &(*p)->next) if(*p == nil) *p = new(nil); if(*p == nil) *p = new(nil); }else{ if(j->t != JSONObject) err("not an object"); if(s[a] == '/') a++; n = 0; while(s[a + n] != '/' && s[a + n] != '[' && s[a + n] && s[a + n] != ':' && s[a + n] != '\n') n++; if(n == 0) err("empty key string"); t = unstring(s + a, s + a + n); for(p = &j->first; *p != nil && strcmp(t, (*p)->name); p = &(*p)->next) ; if(*p == nil) *p = new(t); else free(t); a += n; } j = (*p)->val; } if(s[a++] != ':' || s[a++] != ' ') err("missing colon"); if(j->t != JSONNull) err("new value assigned to old key"); for(n = a; s[a] && s[a] != '\n'; a++) ; if(a - n == 4 && !strncmp(s + n, "true", 4)){ j->t = JSONBool; j->n = 1; }else if(a - n == 5 && !strncmp(s + n, "false", 5)){ j->t = JSONBool; j->n = 0; }else if(a - n != 4 || strncmp(s + n, "null", 4)){ j->n = strtod(s + n, &t); if(t == s + a) j->t = JSONNumber; else{ j->t = JSONString; j->s = unstring(s + n, s + a); } } } }else if((r = jsonparse(s)) == nil) sysfatal("jsonparse: %r"); free(s); if(m == 'p') path(r); else{ pretty(r, 1); cout('\n'); } cout(0); exits(nil); }