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