2.86 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 */