plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

commit c2ceb1af3cd9615eb23ed21332892bd5223c70c6
parent e4352a504acf83163fe41a3c1a557fbb3fb17878
Author: ssnf <ssnf@ssnf.xyz>
Date:   Wed, 27 Aug 2025 16:27:07 +0000

jason

Diffstat:
Minclude/json.h | 2+-
Mman/man1/INDEX | 1+
Aman/man1/json2sdb.1 | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mman/man3/INDEX | 3+++
Aman/man3/json.3 | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/cmd/sdb/json2sdb.c | 9+++++----
Msrc/libjson/json.c | 8++++----
7 files changed, 258 insertions(+), 9 deletions(-)

diff --git a/include/json.h b/include/json.h @@ -33,4 +33,4 @@ typedef struct { void json_close(Json *j); int json_init(Json *j, char *file); -JsonType json_next(Json *j, JsonType t); +int json_next(Json *j, JsonType t); diff --git a/man/man1/INDEX b/man/man1/INDEX @@ -102,6 +102,7 @@ toico jpg.1 topng jpg.1 toppm jpg.1 yuv jpg.1 +json2sdb json2sdb.1 kill kill.1 slay kill.1 start kill.1 diff --git a/man/man1/json2sdb.1 b/man/man1/json2sdb.1 @@ -0,0 +1,63 @@ +.TH JSON2SDB 1 +.SH NAME +json2sdb \- convert JSON to simple database format +.SH SYNOPSIS +.B json2sdb +[ +.I file +] +.SH DESCRIPTION +.I Json2sdb +reads JSON data and converts it to the simple database (sdb) format. +If +.I file +is given, it reads from that file; otherwise it reads from standard input. +.PP +The conversion produces sdb records with a hierarchical path structure +representing the JSON data organization. Each output line contains: +.IP +.EX +type path[key]=value +.EE +.PP +The type character indicates the data type: +.TP +.B o +JSON object +.TP +.B a +JSON array +.TP +.B s +JSON string value +.TP +.B n +JSON numeric value +.PP +The path represents the hierarchical location within the JSON structure. +Root objects start with a leading dot, while root arrays and primitive values +use an empty path. Object properties are separated by dots (.), while array +elements are shown with empty square brackets []. String values containing spaces or special +characters are quoted according to sdb format rules. +.PP +Spaces in keys and paths are encoded as plus signs (+), and certain special +characters are hex-escaped (=, +) to ensure sdb format compatibility. +.SH EXAMPLE +.IP +.EX +% echo '{"name": "alice", "age": 25}' | json2sdb +o +s .name=alice +n .age=25 +.EE +.SH SOURCE +.B \*9/src/cmd/sdb/json2sdb.c +.SH SEE ALSO +.MR json (3) , +.MR sdb (1) , +.MR sdb (7) , +.MR sdbr (3) +.SH DIAGNOSTICS +.I Json2sdb +exits with a non-zero status if JSON parsing fails. +Parse errors include the line number and error description. diff --git a/man/man3/INDEX b/man/man3/INDEX @@ -1221,6 +1221,9 @@ Strtok str.3 Strzero str.3 str str.3 string string.3 +json_close json.3 +json_init json.3 +json_next json.3 runestringnwidth stringsize.3 runestringsize stringsize.3 runestringwidth stringsize.3 diff --git a/man/man3/json.3 b/man/man3/json.3 @@ -0,0 +1,181 @@ +.TH JSON 3 +.SH NAME +json_init, json_next, json_close \- JSON parsing library +.SH SYNOPSIS +.B #include <u.h> +.br +.B #include <libc.h> +.br +.B #include <bio.h> +.br +.B #include <str.h> +.br +.B #include <vec.h> +.br +.B #include <json.h> +.PP +.EX +.ta 6n +\w'JsonType 'u +\w'JStr = 1 << 1, 'u +typedef enum { + JNone, + JNum, + JStr = 1 << 1, + JObj = 1 << 2, + JErr = 1 << 3 +} JsonType; + +typedef union { + String s; + double n; + char c; +} JsonVal; + +typedef struct { + String k; + JsonVal v; + JsonType t; +} JsonObj; + +typedef struct { + ulong st[8]; /* obj stack */ + JsonObj *o; /* obj vector */ + JsonObj *cur; /* cur obj */ + ulong n; /* stack number */ + ... +} Json; +.EE +.PP +.ta \w'\fLJsonType 'u +.B +int json_init(Json *j, char *file) +.PP +.B +int json_next(Json *j, JsonType t) +.PP +.B +void json_close(Json *j) +.SH DESCRIPTION +These functions provide a simple streaming JSON parser. +.PP +.I Json_init +initializes parser +.I j +to read from +.IR file . +If +.I file +is +.BR nil , +input is read from standard input. +Returns 1 on success, or 0 if the file cannot be opened. +.PP +.I Json_next +returns a positive +.I JsonType +value if a JSON element matching filter +.I t +if found, or 0 if no more matching elements exist, or the parser is in an error state. +The filter uses bitwise OR to combine types, or 0 to accept any type. +If an element doesn't match the filter, it is skipped and the next element is processed. +.PP +The current element is stored in +.IB cur +with type +.IB t , +key +.IB k , +and value +.IB v . +For containers, the character '{' or '[' is stored in +.IB v.c +to respectively differentiate between a object or array. +.PP +.I Json_close +frees all memory associated with parser +.IR j . +.SS Types +.TP +.B JNone +No value or end of input +.TP +.B JNum +Numeric value stored in +.IB v.n +.TP +.B JStr +String value stored in +.IB v.s +.TP +.B JObj +Object or array start, character stored in +.IB v.c +.TP +.B JErr +Parse error, message in +.IB k , +line number in +.IB v.n +.SH EXAMPLES +Parse a JSON file and print all key-value pairs: +.IP +.EX +Json j; +JsonObj *o; + +if(!json_init(&j, nil)) + sysfatal("open(): %r"); +while(json_next(&j, 0)){ + o = j.cur; + if(o->t & JObj) + continue; + if(o->k.s) + print("%s = ", o->k.s); + if(o->t == JStr) + print("%s\\n", o->v.s.s); + else + print("%g\\n", o->v.n); +} +if(j.cur->t == JErr) + fprint(2, "%s at line %d", o->k.s, (int)o->v.n); +json_close(&j); +.EE +.PP +Count key-value pairs in a JSON file: +.IP +.EX +Json j; +JsonObj *o; +int n; + +json_init(&j, nil); +for(n = 0; json_next(&j, 0);) + if (j.cur->t & (JStr|JNum)) + ++n; +o = j.cur; +if(o->t == JErr) + sysfatal("%s at line %d", o->k.s, (int)o->v.n); +print("%d pairs\\n", n); +json_close(&j); +.EE +.SH SOURCE +/src/libjson +.SH SEE ALSO +.IR str (3), +.IR vec (3), +.IR sdb (7) +.SH DIAGNOSTICS +.I Json_init +returns 0 if the file cannot be opened. +Parse errors are returned as +.B JErr +elements with error messages stored in +.IB k +and line numbers stored in +.IB v.n . +.SH NOTES +String escape sequences are processed minimally, handling only +quote and backslash characters. +Non-JSON tokens are parsed as literal strings. +.SH BUGS +The parser does not validate JSON structure completely. +Malformed input may cause unexpected results. diff --git a/src/cmd/sdb/json2sdb.c b/src/cmd/sdb/json2sdb.c @@ -17,8 +17,6 @@ JObj2r(Json *j) ulong i; char t, last; - if (j->cur->t == JErr) - sysfatal("%s at line %d", j->cur->k.s, (int)j->cur->v.n); Strzero(&node); last = 0; for (i = 0; i < j->n; ++i) { @@ -40,7 +38,8 @@ JObj2r(Json *j) if (o->t == JObj) { t = (o->v.c == '{') ? 'o' : 'a'; print("%c\t%s\n", t, node.s); - json_next(j, 0); + if (!json_next(j, 0)) + return; JObj2r(j); return; } @@ -67,7 +66,7 @@ Ufmt(Fmt *fmt) case ' ': fmtrune(fmt, '+'); break; case '+': fmtprint(fmt, "\\x2b"); break; case '=': fmtprint(fmt, "\\x3d"); break; - case '%': fmtprint(fmt, "\\x25"); break; + case '\\': fmtprint(fmt, "\\\\"); break; case '\f': fmtprint(fmt, "\\f"); break; case '\n': fmtprint(fmt, "\\n"); break; case '\r': fmtprint(fmt, "\\r"); break; @@ -90,5 +89,7 @@ main(int argc, char *argv[]) json_init(&j, argv[1]); for (;json_next(&j, 0);) JObj2r(&j); + if (j.cur->t == JErr) + sysfatal("%s at line %d", j.cur->k.s, (int)j.cur->v.n); exits(0); } diff --git a/src/libjson/json.c b/src/libjson/json.c @@ -60,7 +60,7 @@ json_init(Json *j, char *file) return 1; } -JsonType +int json_next(Json *j, JsonType t) { JsonObj *o; @@ -134,6 +134,8 @@ loop: case ':': if (j->cur->k.s) return err(j, "double colon"); + if (j->cur->t != JStr) + return err(j, "invalid key"); j->cur->k = j->cur->v.s; memset(&j->cur->v, 0, sizeof(j->cur->v)); j->cur->t = JNone; @@ -157,9 +159,7 @@ loop: j->cur->t = JStr; goto loop; } - if (t == JNone || (t & j->cur->t)) + if (!t || (t & j->cur->t)) return j->cur->t; - if (j->cur->t == JObj) - for (i = j->n - 1; json_next(j, 0) && j->n != i;); goto next; }