commit c2ceb1af3cd9615eb23ed21332892bd5223c70c6
parent e4352a504acf83163fe41a3c1a557fbb3fb17878
Author: ssnf <ssnf@ssnf.xyz>
Date: Wed, 27 Aug 2025 16:27:07 +0000
jason
Diffstat:
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;
}