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