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