2.91
xs_json.h
1/* copyright (c) 2022 - 2026 grunfink et al. / MIT license */
2
3#ifndef _XS_JSON_H
4
5#define _XS_JSON_H
6
7#ifndef MAX_JSON_DEPTH
8#define MAX_JSON_DEPTH 32
9#endif
10
11void xs_json_dump_value(const xs_val *data, int level, int indent, FILE *f);
12int xs_json_dump(const xs_val *data, int indent, FILE *f);
13xs_str *xs_json_dumps(const xs_val *data, int indent);
14
15xs_val *xs_json_load_full(FILE *f, int maxdepth);
16xs_val *xs_json_loads_full(const xs_str *json, int maxdepth);
17#define xs_json_load(f) xs_json_load_full(f, MAX_JSON_DEPTH)
18#define xs_json_loads(s) xs_json_loads_full(s, MAX_JSON_DEPTH)
19
20xstype xs_json_load_type(FILE *f);
21int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c);
22int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, int *c);
23int xs_json_load_array(FILE *f, int maxdepth, xs_list **l);
24int xs_json_load_object(FILE *f, int maxdepth, xs_dict **d);
25
26
27#ifdef XS_IMPLEMENTATION
28
29/** IMPLEMENTATION **/
30
31#include "xs_unicode.h"
32
33/** JSON dumps **/
34
35static void _xs_json_dump_str(const char *data, FILE *f)
36/* dumps a string in JSON format */
37{
38 unsigned char c;
39 fputs("\"", f);
40
41 while ((c = *data)) {
42 if (c == '\n')
43 fputs("\\n", f);
44 else
45 if (c == '\r')
46 fputs("\\r", f);
47 else
48 if (c == '\t')
49 fputs("\\t", f);
50 else
51 if (c == '\\')
52 fputs("\\\\", f);
53 else
54 if (c == '"')
55 fputs("\\\"", f);
56 else
57 if (c < 32)
58 fprintf(f, "\\u%04x", (unsigned int) c);
59 else
60 fputc(c, f);
61
62 data++;
63 }
64
65 fputs("\"", f);
66}
67
68
69static void _xs_json_indent(int level, int indent, FILE *f)
70/* adds indentation */
71{
72 if (indent) {
73 int n;
74
75 fputc('\n', f);
76
77 for (n = 0; n < level * indent; n++)
78 fputc(' ', f);
79 }
80}
81
82
83void xs_json_dump_value(const xs_val *data, int level, int indent, FILE *f)
84/* dumps partial data as JSON */
85{
86 int c = 0;
87 const xs_val *v;
88
89 switch (xs_type(data)) {
90 case XSTYPE_NULL:
91 fputs("null", f);
92 break;
93
94 case XSTYPE_TRUE:
95 fputs("true", f);
96 break;
97
98 case XSTYPE_FALSE:
99 fputs("false", f);
100 break;
101
102 case XSTYPE_NUMBER:
103 fputs(xs_number_str(data), f);
104 break;
105
106 case XSTYPE_LIST:
107 fputc('[', f);
108
109 xs_list_foreach(data, v) {
110 if (c != 0)
111 fputc(',', f);
112
113 _xs_json_indent(level + 1, indent, f);
114 xs_json_dump_value(v, level + 1, indent, f);
115
116 c++;
117 }
118
119 _xs_json_indent(level, indent, f);
120 fputc(']', f);
121
122 break;
123
124 case XSTYPE_DICT:
125 fputc('{', f);
126
127 const xs_str *k;
128
129 xs_dict_foreach(data, k, v) {
130 if (c != 0)
131 fputc(',', f);
132
133 _xs_json_indent(level + 1, indent, f);
134
135 _xs_json_dump_str(k, f);
136 fputc(':', f);
137
138 if (indent)
139 fputc(' ', f);
140
141 xs_json_dump_value(v, level + 1, indent, f);
142
143 c++;
144 }
145
146 _xs_json_indent(level, indent, f);
147 fputc('}', f);
148 break;
149
150 case XSTYPE_STRING:
151 _xs_json_dump_str(data, f);
152 break;
153
154 default:
155 break;
156 }
157}
158
159
160int xs_json_dump(const xs_val *data, int indent, FILE *f)
161/* dumps data into a file as JSON */
162{
163 xstype t = xs_type(data);
164
165 if (t == XSTYPE_LIST || t == XSTYPE_DICT) {
166 xs_json_dump_value(data, 0, indent, f);
167 return 1;
168 }
169
170 return 0;
171}
172
173
174xs_str *xs_json_dumps(const xs_val *data, int indent)
175/* dumps data as a JSON string */
176{
177 xs_str *s = NULL;
178 size_t sz;
179 FILE *f;
180
181 if ((f = open_memstream(&s, &sz)) != NULL) {
182 int r = xs_json_dump(data, indent, f);
183 fclose(f);
184
185 if (!r)
186 s = xs_free(s);
187 }
188
189 return s;
190}
191
192
193/** JSON loads **/
194
195typedef enum {
196 JS_ERROR = -1,
197 JS_INCOMPLETE,
198 JS_OCURLY,
199 JS_OBRACK,
200 JS_CCURLY,
201 JS_CBRACK,
202 JS_COMMA,
203 JS_COLON,
204 JS_VALUE,
205 JS_STRING,
206 JS_NUMBER,
207 JS_TRUE,
208 JS_FALSE,
209 JS_NULL,
210 JS_ARRAY,
211 JS_OBJECT
212} js_type;
213
214
215static xs_val *_xs_json_load_lexer(FILE *f, js_type *t)
216{
217 int c;
218 xs_val *v = NULL;
219 int offset;
220
221 *t = JS_ERROR;
222
223 /* skip blanks */
224 while ((c = fgetc(f)) == ' ' || c == '\t' || c == '\n' || c == '\r');
225
226 if (c == '{')
227 *t = JS_OCURLY;
228 else
229 if (c == '}')
230 *t = JS_CCURLY;
231 else
232 if (c == '[')
233 *t = JS_OBRACK;
234 else
235 if (c == ']')
236 *t = JS_CBRACK;
237 else
238 if (c == ',')
239 *t = JS_COMMA;
240 else
241 if (c == ':')
242 *t = JS_COLON;
243 else
244 if (c == '"') {
245 *t = JS_STRING;
246
247 v = xs_str_new(NULL);
248 offset = 0;
249
250 while ((c = fgetc(f)) != '"' && c != EOF && *t != JS_ERROR) {
251 if (c == '\\') {
252 unsigned int cp = fgetc(f);
253
254 switch (cp) {
255 case 'n': cp = '\n'; break;
256 case 'r': cp = '\r'; break;
257 case 't': cp = '\t'; break;
258 case '"': cp = '"'; break;
259 case '\\': cp = '\\'; break;
260 case '/': cp = '/'; break;
261 case 'u': /* Unicode codepoint as an hex char */
262 if (fscanf(f, "%04x", &cp) != 1) {
263 *t = JS_ERROR;
264 break;
265 }
266
267 if (xs_is_surrogate(cp)) {
268 /* \u must follow */
269 if (fgetc(f) != '\\' || fgetc(f) != 'u') {
270 *t = JS_ERROR;
271 break;
272 }
273
274 unsigned int p2;
275 if (fscanf(f, "%04x", &p2) != 1) {
276 *t = JS_ERROR;
277 break;
278 }
279
280 cp = xs_surrogate_dec(cp, p2);
281 }
282
283 /* replace dangerous control codes with their visual representations */
284 if (cp < ' ' && !strchr("\r\n\t", cp))
285 cp += 0x2400;
286
287 break;
288
289 default:
290 *t = JS_ERROR;
291 break;
292 }
293
294 v = xs_utf8_insert(v, cp, &offset);
295 }
296 else {
297 if (c >= 0 && c < ' ') {
298 v = xs_utf8_insert(v, c + 0x2400, &offset);
299 }
300 else {
301 char cc = c;
302 v = xs_insert_m(v, offset, &cc, 1);
303 offset++;
304 }
305
306 if (!xs_is_string(v)) {
307 *t = JS_ERROR;
308 break;
309 }
310 }
311 }
312
313 if (c == EOF)
314 *t = JS_ERROR;
315 }
316 else
317 if (c == '-' || (c >= '0' && c <= '9') || c == '.') {
318 double d;
319
320 ungetc(c, f);
321 if (fscanf(f, "%lf", &d) == 1) {
322 *t = JS_NUMBER;
323 v = xs_number_new(d);
324 }
325 }
326 else
327 if (c == 't') {
328 if (fgetc(f) == 'r' && fgetc(f) == 'u' && fgetc(f) == 'e') {
329 *t = JS_TRUE;
330 v = xs_val_new(XSTYPE_TRUE);
331 }
332 }
333 else
334 if (c == 'f') {
335 if (fgetc(f) == 'a' && fgetc(f) == 'l' &&
336 fgetc(f) == 's' && fgetc(f) == 'e') {
337 *t = JS_FALSE;
338 v = xs_val_new(XSTYPE_FALSE);
339 }
340 }
341 else
342 if (c == 'n') {
343 if (fgetc(f) == 'u' && fgetc(f) == 'l' && fgetc(f) == 'l') {
344 *t = JS_NULL;
345 v = xs_val_new(XSTYPE_NULL);
346 }
347 }
348
349 if (*t == JS_ERROR)
350 v = xs_free(v);
351
352 return v;
353}
354
355
356int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c)
357/* loads the next scalar value from the JSON stream */
358/* if the value ahead is compound, value is NULL and pt is set */
359{
360 js_type t;
361
362 *value = _xs_json_load_lexer(f, &t);
363
364 if (t == JS_ERROR)
365 return -1;
366
367 if (t == JS_CBRACK)
368 return 0;
369
370 if (*c > 0) {
371 if (t == JS_COMMA)
372 *value = _xs_json_load_lexer(f, &t);
373 else
374 return -1;
375 }
376
377 if (*value == NULL) {
378 /* possible compound type ahead */
379 if (t == JS_OBRACK)
380 *pt = XSTYPE_LIST;
381 else
382 if (t == JS_OCURLY)
383 *pt = XSTYPE_DICT;
384 else
385 return -1;
386 }
387 else
388 *pt = xs_type(*value);
389
390 *c = *c + 1;
391
392 return 1;
393}
394
395
396int xs_json_load_array(FILE *f, int maxdepth, xs_list **l)
397/* loads a full JSON array (after the initial OBRACK) */
398/* l can be NULL for the content to be dropped */
399{
400 xstype t;
401 int r = 0;
402 int c = 0;
403
404 for (;;) {
405 xs *v = NULL;
406 r = xs_json_load_array_iter(f, &v, &t, &c);
407
408 if (r == 1) {
409 /* partial load? */
410 if (v == NULL && maxdepth != 0) {
411 if (t == XSTYPE_LIST) {
412 if (l)
413 v = xs_list_new();
414
415 r = xs_json_load_array(f, maxdepth - 1, &v);
416 }
417 else
418 if (t == XSTYPE_DICT) {
419 if (l)
420 v = xs_dict_new();
421
422 r = xs_json_load_object(f, maxdepth - 1, &v);
423 }
424 }
425
426 /* error? */
427 if (r < 0)
428 break;
429
430 if (l)
431 *l = xs_list_append(*l, v);
432 }
433 else
434 break;
435 }
436
437 if (r < 0 && l)
438 *l = xs_free(*l);
439
440 return r;
441}
442
443
444int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, int *c)
445/* loads the next key and scalar value from the JSON stream */
446/* if the value ahead is compound, value is NULL and pt is set */
447{
448 js_type t;
449
450 *key = _xs_json_load_lexer(f, &t);
451
452 if (t == JS_ERROR)
453 return -1;
454
455 if (t == JS_CCURLY)
456 return 0;
457
458 if (*c > 0) {
459 if (t == JS_COMMA)
460 *key = _xs_json_load_lexer(f, &t);
461 else
462 return -1;
463 }
464
465 if (t != JS_STRING)
466 return -1;
467
468 xs_free(_xs_json_load_lexer(f, &t));
469
470 if (t != JS_COLON)
471 return -1;
472
473 *value = _xs_json_load_lexer(f, &t);
474
475 if (*value == NULL) {
476 /* possible complex type ahead */
477 if (t == JS_OBRACK)
478 *pt = XSTYPE_LIST;
479 else
480 if (t == JS_OCURLY)
481 *pt = XSTYPE_DICT;
482 else
483 return -1;
484 }
485 else
486 *pt = xs_type(*value);
487
488 *c = *c + 1;
489
490 return 1;
491}
492
493
494int xs_json_load_object(FILE *f, int maxdepth, xs_dict **d)
495/* loads a full JSON object (after the initial OCURLY) */
496/* d can be NULL for the content to be dropped */
497{
498 xstype t;
499 int r = 0;
500 int c = 0;
501
502 for (;;) {
503 xs *k = NULL;
504 xs *v = NULL;
505 r = xs_json_load_object_iter(f, &k, &v, &t, &c);
506
507 if (r == 1) {
508 /* partial load? */
509 if (v == NULL && maxdepth != 0) {
510 if (t == XSTYPE_LIST) {
511 if (d)
512 v = xs_list_new();
513
514 r = xs_json_load_array(f, maxdepth - 1, &v);
515 }
516 else
517 if (t == XSTYPE_DICT) {
518 if (d)
519 v = xs_dict_new();
520
521 r = xs_json_load_object(f, maxdepth - 1, &v);
522 }
523 }
524
525 /* error? */
526 if (r < 0)
527 break;
528
529 if (d)
530 *d = xs_dict_append(*d, k, v);
531 }
532 else
533 break;
534 }
535
536 if (r < 0 && d)
537 *d = xs_free(*d);
538
539 return r;
540}
541
542
543xstype xs_json_load_type(FILE *f)
544/* identifies the type of a JSON stream */
545{
546 xstype t = XSTYPE_NULL;
547 js_type jt;
548
549 xs_free(_xs_json_load_lexer(f, &jt));
550
551 if (jt == JS_OBRACK)
552 t = XSTYPE_LIST;
553 else
554 if (jt == JS_OCURLY)
555 t = XSTYPE_DICT;
556
557 return t;
558}
559
560
561xs_val *xs_json_load_full(FILE *f, int maxdepth)
562/* loads a JSON file */
563{
564 xs_val *v = NULL;
565 xstype t = xs_json_load_type(f);
566
567 if (t == XSTYPE_LIST) {
568 v = xs_list_new();
569 xs_json_load_array(f, maxdepth, &v);
570 }
571 else
572 if (t == XSTYPE_DICT) {
573 v = xs_dict_new();
574 xs_json_load_object(f, maxdepth, &v);
575 }
576
577 return v;
578}
579
580
581xs_val *xs_json_loads_full(const xs_str *json, int maxdepth)
582/* loads a string in JSON format and converts to a multiple data */
583{
584 FILE *f;
585 xs_val *v = NULL;
586
587 if ((f = fmemopen((char *)json, strlen(json), "r")) != NULL) {
588 v = xs_json_load_full(f, maxdepth);
589 fclose(f);
590 }
591
592 return v;
593}
594
595
596#endif /* XS_IMPLEMENTATION */
597
598#endif /* _XS_JSON_H */