├── .gitignore ├── LICENSE ├── README.md ├── json.c ├── project.janet └── test └── suite0.janet /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Calvin Rose 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note: Please use spork/json instead. 2 | 3 | ``` 4 | jpm install spork 5 | 6 | ``` 7 | 8 | # JSON 9 | 10 | A JSON module for janet. Encodes and Decodes JSON data and converts it 11 | to and from Janet data structures. Strings are encoded as UTF-8, and UTF-16 12 | escapes and surrogates as supported as per the JSON spec. 13 | 14 | Json values are translated as follows: 15 | 16 | - JSON array becomes Janet array 17 | - JSON string becomes a Janet string. 18 | - JSON Objects becomes a Janet table. 19 | - JSON true and false become Janet booleans. 20 | - JSON null becomes the keyword :null. This is because JSON supports null values in objects, 21 | while Janet does not support nil value or keys in tables. 22 | 23 | ## Building 24 | 25 | To build the native module, use the `jpm tool`, which requires having janet installed. 26 | Run 27 | 28 | ``` 29 | jpm build 30 | ``` 31 | 32 | To build the library. 33 | 34 | ## Testing 35 | 36 | ``` 37 | jpm test 38 | ``` 39 | 40 | ## License 41 | 42 | This module is licensed under the MIT/X11 License. 43 | -------------------------------------------------------------------------------- /json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Calvin Rose 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | /*****************/ 28 | /* JSON Decoding */ 29 | /*****************/ 30 | 31 | #define JSON_KEYWORD_KEY 0x10000 32 | #define JSON_NULL_TO_NIL 0x20000 33 | 34 | /* Check if a character is whitespace */ 35 | static int white(uint8_t c) { 36 | return c == '\t' || c == '\n' || c == ' ' || c == '\r'; 37 | } 38 | 39 | /* Skip whitespace */ 40 | static void skipwhite(const char **p) { 41 | const char *cp = *p; 42 | for (;;) { 43 | if (white(*cp)) 44 | cp++; 45 | else 46 | break; 47 | } 48 | *p = cp; 49 | } 50 | 51 | /* Get a hex digit value */ 52 | static int hexdig(char dig) { 53 | if (dig >= '0' && dig <= '9') 54 | return dig - '0'; 55 | if (dig >= 'a' && dig <= 'f') 56 | return 10 + dig - 'a'; 57 | if (dig >= 'A' && dig <= 'F') 58 | return 10 + dig - 'A'; 59 | return -1; 60 | } 61 | 62 | /* Convert integer to hex character */ 63 | static const char hex_digits[] = "0123456789ABCDEF"; 64 | #define tohex(x) (hex_digits[x]) 65 | 66 | /* Read the hex value for a unicode escape */ 67 | static const char *decode_utf16_escape(const char *p, uint32_t *outpoint) { 68 | if (!p[0] || !p[1] || !p[2] || !p[3]) 69 | return "unexpected end of source"; 70 | int d1 = hexdig(p[0]); 71 | int d2 = hexdig(p[1]); 72 | int d3 = hexdig(p[2]); 73 | int d4 = hexdig(p[3]); 74 | if (d1 < 0 || d2 < 0 || d3 < 0 || d4 < 0) 75 | return "invalid hex digit"; 76 | *outpoint = d4 | (d3 << 4) | (d2 << 8) | (d1 << 12); 77 | return NULL; 78 | } 79 | 80 | /* Parse a string. Also handles the conversion of utf-16 to 81 | * utf-8. */ 82 | static const char *decode_string(const char **p, Janet *out) { 83 | JanetBuffer *buffer = janet_buffer(0); 84 | const char *cp = *p; 85 | while (*cp != '"') { 86 | uint8_t b = (uint8_t) *cp; 87 | if (b < 32) return "invalid character in string"; 88 | if (b == '\\') { 89 | cp++; 90 | switch(*cp) { 91 | default: 92 | return "unknown string escape"; 93 | case 'b': 94 | b = '\b'; 95 | break; 96 | case 'f': 97 | b = '\f'; 98 | break; 99 | case 'n': 100 | b = '\n'; 101 | break; 102 | case 'r': 103 | b = '\r'; 104 | break; 105 | case 't': 106 | b = '\t'; 107 | break; 108 | case '"': 109 | b = '"'; 110 | break; 111 | case '\\': 112 | b = '\\'; 113 | break; 114 | case '/': 115 | b = '/'; 116 | break; 117 | case 'u': 118 | { 119 | /* Get codepoint and check for surrogate pair */ 120 | uint32_t codepoint; 121 | const char *err = decode_utf16_escape(cp + 1, &codepoint); 122 | if (err) return err; 123 | if (codepoint >= 0xDC00 && codepoint <= 0xDFFF) { 124 | return "unexpected utf-16 low surrogate"; 125 | } else if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { 126 | if (cp[5] != '\\') return "expected utf-16 low surrogate pair"; 127 | if (cp[6] != 'u') return "expected utf-16 low surrogate pair"; 128 | uint32_t lowsur; 129 | const char *err = decode_utf16_escape(cp + 7, &lowsur); 130 | if (err) return err; 131 | if (lowsur < 0xDC00 || lowsur > 0xDFFF) 132 | return "expected utf-16 low surrogate pair"; 133 | codepoint = ((codepoint - 0xD800) << 10) + 134 | (lowsur - 0xDC00) + 0x10000; 135 | cp += 11; 136 | } else { 137 | cp += 5; 138 | } 139 | /* Write codepoint */ 140 | if (codepoint <= 0x7F) { 141 | janet_buffer_push_u8(buffer, codepoint); 142 | } else if (codepoint <= 0x7FF) { 143 | janet_buffer_push_u8(buffer, ((codepoint >> 6) & 0x1F) | 0xC0); 144 | janet_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80); 145 | } else if (codepoint <= 0xFFFF) { 146 | janet_buffer_push_u8(buffer, ((codepoint >> 12) & 0x0F) | 0xE0); 147 | janet_buffer_push_u8(buffer, ((codepoint >> 6) & 0x3F) | 0x80); 148 | janet_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80); 149 | } else { 150 | janet_buffer_push_u8(buffer, ((codepoint >> 18) & 0x07) | 0xF0); 151 | janet_buffer_push_u8(buffer, ((codepoint >> 12) & 0x3F) | 0x80); 152 | janet_buffer_push_u8(buffer, ((codepoint >> 6) & 0x3F) | 0x80); 153 | janet_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80); 154 | } 155 | } 156 | continue; 157 | } 158 | } 159 | janet_buffer_push_u8(buffer, b); 160 | cp++; 161 | } 162 | *out = janet_stringv(buffer->data, buffer->count); 163 | *p = cp + 1; 164 | return NULL; 165 | } 166 | 167 | static const char *decode_one(const char **p, Janet *out, int depth) { 168 | 169 | /* Prevent stack overflow */ 170 | if ((depth & 0xFFFF) > JANET_RECURSION_GUARD) goto recurdepth; 171 | 172 | /* Skip leading whitepspace */ 173 | skipwhite(p); 174 | 175 | /* Main switch */ 176 | switch (**p) { 177 | default: 178 | goto badchar; 179 | case '\0': 180 | goto eos; 181 | /* Numbers */ 182 | case '-': case '0': case '1' : case '2': case '3' : case '4': 183 | case '5': case '6': case '7' : case '8': case '9': 184 | { 185 | errno = 0; 186 | char *end = NULL; 187 | double x = strtod(*p, &end); 188 | if (end == *p) goto badnum; 189 | *p = end; 190 | *out = janet_wrap_number(x); 191 | break; 192 | } 193 | /* false, null, true */ 194 | case 'f': 195 | { 196 | const char *cp = *p; 197 | if (cp[1] != 'a' || cp[2] != 'l' || cp[3] != 's' || cp[4] != 'e') 198 | goto badident; 199 | *out = janet_wrap_false(); 200 | *p = cp + 5; 201 | break; 202 | } 203 | case 'n': 204 | { 205 | const char *cp = *p; 206 | 207 | if (cp[1] != 'u' || cp[2] != 'l' || cp[3] != 'l') 208 | goto badident; 209 | if (depth & JSON_NULL_TO_NIL) { 210 | *out = janet_wrap_nil(); 211 | } else { 212 | *out = janet_ckeywordv("null"); 213 | } 214 | *p = cp + 4; 215 | break; 216 | } 217 | case 't': 218 | { 219 | const char *cp = *p; 220 | if (cp[1] != 'r' || cp[2] != 'u' || cp[3] != 'e') 221 | goto badident; 222 | *out = janet_wrap_true(); 223 | *p = cp + 4; 224 | break; 225 | } 226 | /* String */ 227 | case '"': 228 | { 229 | const char *cp = *p + 1; 230 | const char *start = cp; 231 | while ((*cp >= 32 || *cp < 0) && *cp != '"' && *cp != '\\') 232 | cp++; 233 | /* Only use a buffer for strings with escapes, else just copy 234 | * memory from source */ 235 | if (*cp == '\\') { 236 | *p = *p + 1; 237 | const char *err = decode_string(p, out); 238 | if (err) return err; 239 | break; 240 | } 241 | if (*cp != '"') goto badchar; 242 | *p = cp + 1; 243 | *out = janet_stringv((const uint8_t *)start, cp - start); 244 | break; 245 | } 246 | /* Array */ 247 | case '[': 248 | { 249 | *p = *p + 1; 250 | JanetArray *array = janet_array(0); 251 | const char *err; 252 | Janet subval; 253 | skipwhite(p); 254 | while (**p != ']') { 255 | err = decode_one(p, &subval, depth + 1); 256 | if (err) return err; 257 | janet_array_push(array, subval); 258 | skipwhite(p); 259 | if (**p == ']') break; 260 | if (**p != ',') goto wantcomma; 261 | *p = *p + 1; 262 | } 263 | *p = *p + 1; 264 | *out = janet_wrap_array(array); 265 | } 266 | break; 267 | /* Object */ 268 | case '{': 269 | { 270 | *p = *p + 1; 271 | JanetTable *table = janet_table(0); 272 | const char *err; 273 | Janet subkey, subval; 274 | skipwhite(p); 275 | while (**p != '}') { 276 | skipwhite(p); 277 | if (**p != '"') goto wantstring; 278 | err = decode_one(p, &subkey, depth + 1); 279 | if (err) return err; 280 | skipwhite(p); 281 | if (**p != ':') goto wantcolon; 282 | *p = *p + 1; 283 | err = decode_one(p, &subval, depth + 1); 284 | if (err) return err; 285 | if (depth & JSON_KEYWORD_KEY) { 286 | JanetString str = janet_unwrap_string(subkey); 287 | subkey = janet_keywordv(str, janet_string_length(str)); 288 | } 289 | janet_table_put(table, subkey, subval); 290 | skipwhite(p); 291 | if (**p == '}') break; 292 | if (**p != ',') goto wantcomma; 293 | *p = *p + 1; 294 | } 295 | *p = *p + 1; 296 | *out = janet_wrap_table(table); 297 | break; 298 | } 299 | } 300 | 301 | /* Good return */ 302 | return NULL; 303 | 304 | /* Errors */ 305 | recurdepth: 306 | return "recured too deeply"; 307 | eos: 308 | return "unexpected end of source"; 309 | badident: 310 | return "bad identifier"; 311 | badnum: 312 | return "bad number"; 313 | wantcomma: 314 | return "expected comma"; 315 | wantcolon: 316 | return "expected colon"; 317 | badchar: 318 | return "unexpected character"; 319 | wantstring: 320 | return "expected json string"; 321 | } 322 | 323 | static Janet json_decode(int32_t argc, Janet *argv) { 324 | janet_arity(argc, 1, 3); 325 | Janet ret = janet_wrap_nil(); 326 | const char *err; 327 | const char *start; 328 | const char *p; 329 | if (janet_checktype(argv[0], JANET_BUFFER)) { 330 | JanetBuffer *buffer = janet_unwrap_buffer(argv[0]); 331 | /* Ensure 0 padded */ 332 | janet_buffer_push_u8(buffer, 0); 333 | buffer->count--; 334 | start = p = (const char *)buffer->data; 335 | } else { 336 | JanetByteView bytes = janet_getbytes(argv, 0); 337 | start = p = (const char *)bytes.bytes; 338 | } 339 | int flags = 0; 340 | if (argc > 1 && janet_truthy(argv[1])) flags |= JSON_KEYWORD_KEY; 341 | if (argc > 2 && janet_truthy(argv[2])) flags |= JSON_NULL_TO_NIL; 342 | err = decode_one(&p, &ret, flags); 343 | /* Check trailing values */ 344 | if (!err) { 345 | skipwhite(&p); 346 | if (*p) err = "unexpected extra token"; 347 | } 348 | if (err) 349 | janet_panicf("decode error at position %d: %s", p - start, err); 350 | return ret; 351 | } 352 | 353 | /*****************/ 354 | /* JSON Encoding */ 355 | /*****************/ 356 | 357 | typedef struct { 358 | JanetBuffer *buffer; 359 | int32_t indent; 360 | const uint8_t *tab; 361 | const uint8_t *newline; 362 | int32_t tablen; 363 | int32_t newlinelen; 364 | } Encoder; 365 | 366 | static void encode_newline(Encoder *e) { 367 | janet_buffer_push_bytes(e->buffer, e->newline, e->newlinelen); 368 | /* Skip loop if no tab string */ 369 | if (!e->tablen) return; 370 | for (int32_t i = 0; i < e->indent; i++) 371 | janet_buffer_push_bytes(e->buffer, e->tab, e->tablen); 372 | } 373 | 374 | static const char *encode_one(Encoder *e, Janet x, int depth) { 375 | switch(janet_type(x)) { 376 | default: 377 | goto badtype; 378 | case JANET_NIL: 379 | janet_buffer_push_cstring(e->buffer, "null"); 380 | break; 381 | case JANET_BOOLEAN: 382 | janet_buffer_push_cstring(e->buffer, 383 | janet_unwrap_boolean(x) ? "true" : "false"); 384 | break; 385 | case JANET_NUMBER: 386 | { 387 | char cbuf[25]; 388 | sprintf(cbuf, "%.17g", janet_unwrap_number(x)); 389 | janet_buffer_push_cstring(e->buffer, cbuf); 390 | } 391 | break; 392 | case JANET_STRING: 393 | case JANET_SYMBOL: 394 | case JANET_KEYWORD: 395 | case JANET_BUFFER: 396 | { 397 | const uint8_t *bytes; 398 | const uint8_t *c; 399 | const uint8_t *end; 400 | int32_t len; 401 | janet_bytes_view(x, &bytes, &len); 402 | janet_buffer_push_u8(e->buffer, '"'); 403 | c = bytes; 404 | end = bytes + len; 405 | while (c < end) { 406 | 407 | /* get codepoint */ 408 | uint32_t codepoint; 409 | if (*c < 0x80) { 410 | /* one byte */ 411 | codepoint = *c++; 412 | } else if (*c < 0xE0) { 413 | /* two bytes */ 414 | if (c + 2 > end) goto invalidutf8; 415 | if ((c[1] >> 6) != 2) goto invalidutf8; 416 | codepoint = ((c[0] & 0x1F) << 6) | 417 | (c[1] & 0x3F); 418 | c += 2; 419 | } else if (*c < 0xF0) { 420 | /* three bytes */ 421 | if (c + 3 > end) goto invalidutf8; 422 | if ((c[1] >> 6) != 2) goto invalidutf8; 423 | if ((c[2] >> 6) != 2) goto invalidutf8; 424 | codepoint = ((c[0] & 0x0F) << 12) | 425 | ((c[1] & 0x3F) << 6) | 426 | (c[2] & 0x3F); 427 | c += 3; 428 | } else if (*c < 0xF8) { 429 | /* four bytes */ 430 | if (c + 4 > end) goto invalidutf8; 431 | if ((c[1] >> 6) != 2) goto invalidutf8; 432 | if ((c[2] >> 6) != 2) goto invalidutf8; 433 | if ((c[3] >> 6) != 2) goto invalidutf8; 434 | codepoint = ((c[0] & 0x07) << 18) | 435 | ((c[1] & 0x3F) << 12) | 436 | ((c[3] & 0x3F) << 6) | 437 | (c[3] & 0x3F); 438 | c += 4; 439 | } else { 440 | /* invalid */ 441 | goto invalidutf8; 442 | } 443 | 444 | /* write codepoint */ 445 | if (codepoint > 0x1F && codepoint < 0x80) { 446 | /* Normal, no escape */ 447 | if (codepoint == '\\' || codepoint == '"') 448 | janet_buffer_push_u8(e->buffer, '\\'); 449 | janet_buffer_push_u8(e->buffer, (uint8_t) codepoint); 450 | } else if (codepoint < 0x10000) { 451 | /* One unicode escape */ 452 | uint8_t buf[6]; 453 | buf[0] = '\\'; 454 | buf[1] = 'u'; 455 | buf[2] = tohex((codepoint >> 12) & 0xF); 456 | buf[3] = tohex((codepoint >> 8) & 0xF); 457 | buf[4] = tohex((codepoint >> 4) & 0xF); 458 | buf[5] = tohex(codepoint & 0xF); 459 | janet_buffer_push_bytes(e->buffer, buf, sizeof(buf)); 460 | } else { 461 | /* Two unicode escapes (surrogate pair) */ 462 | uint32_t hi, lo; 463 | uint8_t buf[12]; 464 | hi = ((codepoint - 0x10000) >> 10) + 0xD800; 465 | lo = ((codepoint - 0x10000) & 0x3FF) + 0xDC00; 466 | buf[0] = '\\'; 467 | buf[1] = 'u'; 468 | buf[2] = tohex((hi >> 12) & 0xF); 469 | buf[3] = tohex((hi >> 8) & 0xF); 470 | buf[4] = tohex((hi >> 4) & 0xF); 471 | buf[5] = tohex(hi & 0xF); 472 | buf[6] = '\\'; 473 | buf[7] = 'u'; 474 | buf[8] = tohex((lo >> 12) & 0xF); 475 | buf[9] = tohex((lo >> 8) & 0xF); 476 | buf[10] = tohex((lo >> 4) & 0xF); 477 | buf[11] = tohex(lo & 0xF); 478 | janet_buffer_push_bytes(e->buffer, buf, sizeof(buf)); 479 | } 480 | } 481 | janet_buffer_push_u8(e->buffer, '"'); 482 | } 483 | break; 484 | case JANET_TUPLE: 485 | case JANET_ARRAY: 486 | { 487 | const char *err; 488 | const Janet *items; 489 | int32_t len; 490 | janet_indexed_view(x, &items, &len); 491 | janet_buffer_push_u8(e->buffer, '['); 492 | e->indent++; 493 | for (int32_t i = 0; i < len; i++) { 494 | encode_newline(e); 495 | if ((err = encode_one(e, items[i], depth + 1))) return err; 496 | janet_buffer_push_u8(e->buffer, ','); 497 | } 498 | e->indent--; 499 | if (e->buffer->data[e->buffer->count - 1] == ',') { 500 | e->buffer->count--; 501 | encode_newline(e); 502 | } 503 | janet_buffer_push_u8(e->buffer, ']'); 504 | } 505 | break; 506 | case JANET_TABLE: 507 | case JANET_STRUCT: 508 | { 509 | const char *err; 510 | const JanetKV *kvs; 511 | int32_t count, capacity; 512 | janet_dictionary_view(x, &kvs, &count, &capacity); 513 | janet_buffer_push_u8(e->buffer, '{'); 514 | e->indent++; 515 | for (int32_t i = 0; i < capacity; i++) { 516 | if (janet_checktype(kvs[i].key, JANET_NIL)) 517 | continue; 518 | if (!janet_checktypes(kvs[i].key, JANET_TFLAG_BYTES)) 519 | return "object key must be a byte sequence"; 520 | encode_newline(e); 521 | if ((err = encode_one(e, kvs[i].key, depth + 1))) 522 | return err; 523 | const char *sep = e->tablen ? ": " : ":"; 524 | janet_buffer_push_cstring(e->buffer, sep); 525 | if ((err = encode_one(e, kvs[i].value, depth + 1))) 526 | return err; 527 | janet_buffer_push_u8(e->buffer, ','); 528 | } 529 | e->indent--; 530 | if (e->buffer->data[e->buffer->count - 1] == ',') { 531 | e->buffer->count--; 532 | encode_newline(e); 533 | } 534 | janet_buffer_push_u8(e->buffer, '}'); 535 | } 536 | break; 537 | } 538 | return NULL; 539 | 540 | /* Errors */ 541 | 542 | badtype: 543 | return "type not supported"; 544 | invalidutf8: 545 | return "string contains invalid utf-8"; 546 | } 547 | 548 | static Janet json_encode(int32_t argc, Janet *argv) { 549 | janet_arity(argc, 1, 4); 550 | Encoder e; 551 | e.indent = 0; 552 | e.buffer = janet_optbuffer(argv, argc, 3, 10); 553 | e.tab = NULL; 554 | e.newline = NULL; 555 | e.tablen = 0; 556 | e.newlinelen = 0; 557 | if (argc >= 2) { 558 | JanetByteView tab = janet_getbytes(argv, 1); 559 | e.tab = tab.bytes; 560 | e.tablen = tab.len; 561 | if (argc >= 3) { 562 | JanetByteView newline = janet_getbytes(argv, 2); 563 | e.newline = newline.bytes; 564 | e.newlinelen = newline.len; 565 | } else { 566 | e.newline = (const uint8_t *)"\r\n"; 567 | e.newlinelen = 2; 568 | } 569 | } 570 | const char *err = encode_one(&e, argv[0], 0); 571 | if (err) janet_panicf("encode error: %s", err); 572 | return janet_wrap_buffer(e.buffer); 573 | } 574 | 575 | /****************/ 576 | /* Module Entry */ 577 | /****************/ 578 | 579 | static const JanetReg cfuns[] = { 580 | {"encode", json_encode, 581 | "(json/encode x &opt tab newline buf)\n\n" 582 | "Encodes a janet value in JSON (utf-8). tab and newline are optional byte sequence which are used " 583 | "to format the output JSON. if buf is provided, the formated JSON is append to buf instead of a new buffer. " 584 | "Returns the modifed buffer." 585 | }, 586 | {"decode", json_decode, 587 | "(json/decode json-source &opt keywords nils)\n\n" 588 | "Returns a janet object after parsing JSON. If keywords is truthy, string " 589 | "keys will be converted to keywords. If nils is truthy, null will become nil instead " 590 | "of the keyword :null." 591 | }, 592 | {NULL, NULL, NULL} 593 | }; 594 | 595 | JANET_MODULE_ENTRY(JanetTable *env) { 596 | janet_cfuns(env, "json", cfuns); 597 | } 598 | -------------------------------------------------------------------------------- /project.janet: -------------------------------------------------------------------------------- 1 | (declare-project 2 | :name "json" 3 | :description "Encodes and decodes JSON data, converting it to and from Janet data structures." 4 | :author "Calvin Rose" 5 | :license "MIT" 6 | :url "https://github.com/janet-lang/json" 7 | :repo "git+https://github.com/janet-lang/json.git") 8 | 9 | (declare-native 10 | :name "json" 11 | :source @["json.c"]) 12 | -------------------------------------------------------------------------------- /test/suite0.janet: -------------------------------------------------------------------------------- 1 | (import json :as json) 2 | 3 | (defn check-object [x] 4 | (def y (json/decode (json/encode x))) 5 | (def y1 (json/decode (json/encode x " " "\n"))) 6 | (if (deep-not= x y) (error (string/format "failed roundtrip 1: %p" x))) 7 | (if (deep-not= x y1) (error (string/format "failed roundtrip 2: %p" x)))) 8 | 9 | (check-object 1) 10 | (check-object 100) 11 | (check-object true) 12 | (check-object false) 13 | (check-object (range 1000)) 14 | (check-object @{"two" 2 "four" 4 "six" 6}) 15 | (check-object @{"hello" "world"}) 16 | (check-object @{"john" 1 "billy" "joe" "a" @[1 2 3 4 -1000]}) 17 | (check-object @{"john" 1 "∀abcd" "joe" "a" @[1 2 3 4 -1000]}) 18 | (check-object 19 | "ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ 20 | ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ 21 | ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬") 22 | (check-object @["šč"]) 23 | 24 | # Decoding utf-8 strings 25 | (if (deep-not= "šč" (json/decode `"šč"`)) "did not decode utf-8 string correctly") 26 | --------------------------------------------------------------------------------