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