2.89
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 'u': /* Unicode codepoint as an hex char */
259 if (fscanf(f, "%04x", &cp) != 1) {
260 *t = JS_ERROR;
261 break;
262 }
263
264 if (xs_is_surrogate(cp)) {
265 /* \u must follow */
266 if (fgetc(f) != '\\' || fgetc(f) != 'u') {
267 *t = JS_ERROR;
268 break;
269 }
270
271 unsigned int p2;
272 if (fscanf(f, "%04x", &p2) != 1) {
273 *t = JS_ERROR;
274 break;
275 }
276
277 cp = xs_surrogate_dec(cp, p2);
278 }
279
280 /* replace dangerous control codes with their visual representations */
281 if (cp < ' ' && !strchr("\r\n\t", cp))
282 cp += 0x2400;
283
284 break;
285 }
286
287 v = xs_utf8_insert(v, cp, &offset);
288 }
289 else {
290 char cc = c;
291 v = xs_insert_m(v, offset, &cc, 1);
292
293 if (!xs_is_string(v)) {
294 *t = JS_ERROR;
295 break;
296 }
297
298 offset++;
299 }
300 }
301
302 if (c == EOF)
303 *t = JS_ERROR;
304 }
305 else
306 if (c == '-' || (c >= '0' && c <= '9') || c == '.') {
307 double d;
308
309 ungetc(c, f);
310 if (fscanf(f, "%lf", &d) == 1) {
311 *t = JS_NUMBER;
312 v = xs_number_new(d);
313 }
314 }
315 else
316 if (c == 't') {
317 if (fgetc(f) == 'r' && fgetc(f) == 'u' && fgetc(f) == 'e') {
318 *t = JS_TRUE;
319 v = xs_val_new(XSTYPE_TRUE);
320 }
321 }
322 else
323 if (c == 'f') {
324 if (fgetc(f) == 'a' && fgetc(f) == 'l' &&
325 fgetc(f) == 's' && fgetc(f) == 'e') {
326 *t = JS_FALSE;
327 v = xs_val_new(XSTYPE_FALSE);
328 }
329 }
330 else
331 if (c == 'n') {
332 if (fgetc(f) == 'u' && fgetc(f) == 'l' && fgetc(f) == 'l') {
333 *t = JS_NULL;
334 v = xs_val_new(XSTYPE_NULL);
335 }
336 }
337
338 if (*t == JS_ERROR)
339 v = xs_free(v);
340
341 return v;
342}
343
344
345int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c)
346/* loads the next scalar value from the JSON stream */
347/* if the value ahead is compound, value is NULL and pt is set */
348{
349 js_type t;
350
351 *value = _xs_json_load_lexer(f, &t);
352
353 if (t == JS_ERROR)
354 return -1;
355
356 if (t == JS_CBRACK)
357 return 0;
358
359 if (*c > 0) {
360 if (t == JS_COMMA)
361 *value = _xs_json_load_lexer(f, &t);
362 else
363 return -1;
364 }
365
366 if (*value == NULL) {
367 /* possible compound type ahead */
368 if (t == JS_OBRACK)
369 *pt = XSTYPE_LIST;
370 else
371 if (t == JS_OCURLY)
372 *pt = XSTYPE_DICT;
373 else
374 return -1;
375 }
376 else
377 *pt = xs_type(*value);
378
379 *c = *c + 1;
380
381 return 1;
382}
383
384
385int xs_json_load_array(FILE *f, int maxdepth, xs_list **l)
386/* loads a full JSON array (after the initial OBRACK) */
387/* l can be NULL for the content to be dropped */
388{
389 xstype t;
390 int r = 0;
391 int c = 0;
392
393 for (;;) {
394 xs *v = NULL;
395 r = xs_json_load_array_iter(f, &v, &t, &c);
396
397 if (r == 1) {
398 /* partial load? */
399 if (v == NULL && maxdepth != 0) {
400 if (t == XSTYPE_LIST) {
401 if (l)
402 v = xs_list_new();
403
404 r = xs_json_load_array(f, maxdepth - 1, &v);
405 }
406 else
407 if (t == XSTYPE_DICT) {
408 if (l)
409 v = xs_dict_new();
410
411 r = xs_json_load_object(f, maxdepth - 1, &v);
412 }
413 }
414
415 /* error? */
416 if (r < 0)
417 break;
418
419 if (l)
420 *l = xs_list_append(*l, v);
421 }
422 else
423 break;
424 }
425
426 if (r < 0 && l)
427 *l = xs_free(*l);
428
429 return r;
430}
431
432
433int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, int *c)
434/* loads the next key and scalar value from the JSON stream */
435/* if the value ahead is compound, value is NULL and pt is set */
436{
437 js_type t;
438
439 *key = _xs_json_load_lexer(f, &t);
440
441 if (t == JS_ERROR)
442 return -1;
443
444 if (t == JS_CCURLY)
445 return 0;
446
447 if (*c > 0) {
448 if (t == JS_COMMA)
449 *key = _xs_json_load_lexer(f, &t);
450 else
451 return -1;
452 }
453
454 if (t != JS_STRING)
455 return -1;
456
457 xs_free(_xs_json_load_lexer(f, &t));
458
459 if (t != JS_COLON)
460 return -1;
461
462 *value = _xs_json_load_lexer(f, &t);
463
464 if (*value == NULL) {
465 /* possible complex type ahead */
466 if (t == JS_OBRACK)
467 *pt = XSTYPE_LIST;
468 else
469 if (t == JS_OCURLY)
470 *pt = XSTYPE_DICT;
471 else
472 return -1;
473 }
474 else
475 *pt = xs_type(*value);
476
477 *c = *c + 1;
478
479 return 1;
480}
481
482
483int xs_json_load_object(FILE *f, int maxdepth, xs_dict **d)
484/* loads a full JSON object (after the initial OCURLY) */
485/* d can be NULL for the content to be dropped */
486{
487 xstype t;
488 int r = 0;
489 int c = 0;
490
491 for (;;) {
492 xs *k = NULL;
493 xs *v = NULL;
494 r = xs_json_load_object_iter(f, &k, &v, &t, &c);
495
496 if (r == 1) {
497 /* partial load? */
498 if (v == NULL && maxdepth != 0) {
499 if (t == XSTYPE_LIST) {
500 if (d)
501 v = xs_list_new();
502
503 r = xs_json_load_array(f, maxdepth - 1, &v);
504 }
505 else
506 if (t == XSTYPE_DICT) {
507 if (d)
508 v = xs_dict_new();
509
510 r = xs_json_load_object(f, maxdepth - 1, &v);
511 }
512 }
513
514 /* error? */
515 if (r < 0)
516 break;
517
518 if (d)
519 *d = xs_dict_append(*d, k, v);
520 }
521 else
522 break;
523 }
524
525 if (r < 0 && d)
526 *d = xs_free(*d);
527
528 return r;
529}
530
531
532xstype xs_json_load_type(FILE *f)
533/* identifies the type of a JSON stream */
534{
535 xstype t = XSTYPE_NULL;
536 js_type jt;
537
538 xs_free(_xs_json_load_lexer(f, &jt));
539
540 if (jt == JS_OBRACK)
541 t = XSTYPE_LIST;
542 else
543 if (jt == JS_OCURLY)
544 t = XSTYPE_DICT;
545
546 return t;
547}
548
549
550xs_val *xs_json_load_full(FILE *f, int maxdepth)
551/* loads a JSON file */
552{
553 xs_val *v = NULL;
554 xstype t = xs_json_load_type(f);
555
556 if (t == XSTYPE_LIST) {
557 v = xs_list_new();
558 xs_json_load_array(f, maxdepth, &v);
559 }
560 else
561 if (t == XSTYPE_DICT) {
562 v = xs_dict_new();
563 xs_json_load_object(f, maxdepth, &v);
564 }
565
566 return v;
567}
568
569
570xs_val *xs_json_loads_full(const xs_str *json, int maxdepth)
571/* loads a string in JSON format and converts to a multiple data */
572{
573 FILE *f;
574 xs_val *v = NULL;
575
576 if ((f = fmemopen((char *)json, strlen(json), "r")) != NULL) {
577 v = xs_json_load_full(f, maxdepth);
578 fclose(f);
579 }
580
581 return v;
582}
583
584
585#endif /* XS_IMPLEMENTATION */
586
587#endif /* _XS_JSON_H */