diff options
| author | João Távora | 2018-06-08 02:35:50 +0100 |
|---|---|---|
| committer | João Távora | 2018-06-15 00:13:00 +0100 |
| commit | 51adab5de24b3ee215fe636aedb7ff91d69a220c (patch) | |
| tree | 8664618622113e4890c574d07fa2e025424b951b /src/json.c | |
| parent | 9348039ed45c8e493e8bfef0220249d4d31ef6da (diff) | |
| download | emacs-51adab5de24b3ee215fe636aedb7ff91d69a220c.tar.gz emacs-51adab5de24b3ee215fe636aedb7ff91d69a220c.zip | |
Also allow custom false and null when serializing to JSON
* doc/lispref/text.texi (Parsing JSON): Describe new arguments of
json-serialize and json-insert.
* src/json.c (enum json_object_type, struct json_configuration):
Move up in file before first usage.
(lisp_to_json_toplevel, lisp_to_json_toplevel_1, lisp_to_json):
Accept a struct json_configuration*.
(Fjson_serialize, Fjson_insert): Accept multiple args.
(json_parse_args): Accept new boolean configure_object_type.
* test/src/json-tests.el
(json-serialize, json-insert): Update forward decls.
(json-parse-with-custom-null-and-false-objects): Add assertions for
json-serialize.
Diffstat (limited to 'src/json.c')
| -rw-r--r-- | src/json.c | 195 |
1 files changed, 113 insertions, 82 deletions
diff --git a/src/json.c b/src/json.c index e86ef237d03..d30c997da4c 100644 --- a/src/json.c +++ b/src/json.c | |||
| @@ -325,12 +325,25 @@ json_check_utf8 (Lisp_Object string) | |||
| 325 | CHECK_TYPE (utf8_string_p (string), Qutf_8_string_p, string); | 325 | CHECK_TYPE (utf8_string_p (string), Qutf_8_string_p, string); |
| 326 | } | 326 | } |
| 327 | 327 | ||
| 328 | static json_t *lisp_to_json (Lisp_Object); | 328 | enum json_object_type { |
| 329 | json_object_hashtable, | ||
| 330 | json_object_alist, | ||
| 331 | json_object_plist | ||
| 332 | }; | ||
| 333 | |||
| 334 | struct json_configuration { | ||
| 335 | enum json_object_type object_type; | ||
| 336 | Lisp_Object null_object; | ||
| 337 | Lisp_Object false_object; | ||
| 338 | }; | ||
| 339 | |||
| 340 | static json_t *lisp_to_json (Lisp_Object, struct json_configuration *conf); | ||
| 329 | 341 | ||
| 330 | /* Convert a Lisp object to a toplevel JSON object (array or object). */ | 342 | /* Convert a Lisp object to a toplevel JSON object (array or object). */ |
| 331 | 343 | ||
| 332 | static json_t * | 344 | static json_t * |
| 333 | lisp_to_json_toplevel_1 (Lisp_Object lisp) | 345 | lisp_to_json_toplevel_1 (Lisp_Object lisp, |
| 346 | struct json_configuration *conf) | ||
| 334 | { | 347 | { |
| 335 | json_t *json; | 348 | json_t *json; |
| 336 | ptrdiff_t count; | 349 | ptrdiff_t count; |
| @@ -344,7 +357,8 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp) | |||
| 344 | for (ptrdiff_t i = 0; i < size; ++i) | 357 | for (ptrdiff_t i = 0; i < size; ++i) |
| 345 | { | 358 | { |
| 346 | int status | 359 | int status |
| 347 | = json_array_append_new (json, lisp_to_json (AREF (lisp, i))); | 360 | = json_array_append_new (json, lisp_to_json (AREF (lisp, i), |
| 361 | conf)); | ||
| 348 | if (status == -1) | 362 | if (status == -1) |
| 349 | json_out_of_memory (); | 363 | json_out_of_memory (); |
| 350 | } | 364 | } |
| @@ -369,7 +383,8 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp) | |||
| 369 | if (json_object_get (json, key_str) != NULL) | 383 | if (json_object_get (json, key_str) != NULL) |
| 370 | wrong_type_argument (Qjson_value_p, lisp); | 384 | wrong_type_argument (Qjson_value_p, lisp); |
| 371 | int status = json_object_set_new (json, key_str, | 385 | int status = json_object_set_new (json, key_str, |
| 372 | lisp_to_json (HASH_VALUE (h, i))); | 386 | lisp_to_json (HASH_VALUE (h, i), |
| 387 | conf)); | ||
| 373 | if (status == -1) | 388 | if (status == -1) |
| 374 | { | 389 | { |
| 375 | /* A failure can be caused either by an invalid key or | 390 | /* A failure can be caused either by an invalid key or |
| @@ -424,7 +439,8 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp) | |||
| 424 | if (json_object_get (json, key_str) == NULL) | 439 | if (json_object_get (json, key_str) == NULL) |
| 425 | { | 440 | { |
| 426 | int status | 441 | int status |
| 427 | = json_object_set_new (json, key_str, lisp_to_json (value)); | 442 | = json_object_set_new (json, key_str, lisp_to_json (value, |
| 443 | conf)); | ||
| 428 | if (status == -1) | 444 | if (status == -1) |
| 429 | json_out_of_memory (); | 445 | json_out_of_memory (); |
| 430 | } | 446 | } |
| @@ -444,11 +460,11 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp) | |||
| 444 | hashtable, alist, or plist. */ | 460 | hashtable, alist, or plist. */ |
| 445 | 461 | ||
| 446 | static json_t * | 462 | static json_t * |
| 447 | lisp_to_json_toplevel (Lisp_Object lisp) | 463 | lisp_to_json_toplevel (Lisp_Object lisp, struct json_configuration *conf) |
| 448 | { | 464 | { |
| 449 | if (++lisp_eval_depth > max_lisp_eval_depth) | 465 | if (++lisp_eval_depth > max_lisp_eval_depth) |
| 450 | xsignal0 (Qjson_object_too_deep); | 466 | xsignal0 (Qjson_object_too_deep); |
| 451 | json_t *json = lisp_to_json_toplevel_1 (lisp); | 467 | json_t *json = lisp_to_json_toplevel_1 (lisp, conf); |
| 452 | --lisp_eval_depth; | 468 | --lisp_eval_depth; |
| 453 | return json; | 469 | return json; |
| 454 | } | 470 | } |
| @@ -458,11 +474,11 @@ lisp_to_json_toplevel (Lisp_Object lisp) | |||
| 458 | JSON object. */ | 474 | JSON object. */ |
| 459 | 475 | ||
| 460 | static json_t * | 476 | static json_t * |
| 461 | lisp_to_json (Lisp_Object lisp) | 477 | lisp_to_json (Lisp_Object lisp, struct json_configuration *conf) |
| 462 | { | 478 | { |
| 463 | if (EQ (lisp, QCnull)) | 479 | if (EQ (lisp, conf->null_object)) |
| 464 | return json_check (json_null ()); | 480 | return json_check (json_null ()); |
| 465 | else if (EQ (lisp, QCfalse)) | 481 | else if (EQ (lisp, conf->false_object)) |
| 466 | return json_check (json_false ()); | 482 | return json_check (json_false ()); |
| 467 | else if (EQ (lisp, Qt)) | 483 | else if (EQ (lisp, Qt)) |
| 468 | return json_check (json_true ()); | 484 | return json_check (json_true ()); |
| @@ -488,21 +504,78 @@ lisp_to_json (Lisp_Object lisp) | |||
| 488 | } | 504 | } |
| 489 | 505 | ||
| 490 | /* LISP now must be a vector, hashtable, alist, or plist. */ | 506 | /* LISP now must be a vector, hashtable, alist, or plist. */ |
| 491 | return lisp_to_json_toplevel (lisp); | 507 | return lisp_to_json_toplevel (lisp, conf); |
| 492 | } | 508 | } |
| 493 | 509 | ||
| 494 | DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, 1, NULL, | 510 | static void |
| 511 | json_parse_args (ptrdiff_t nargs, | ||
| 512 | Lisp_Object *args, | ||
| 513 | struct json_configuration *conf, | ||
| 514 | bool configure_object_type) | ||
| 515 | { | ||
| 516 | if ((nargs % 2) != 0) | ||
| 517 | wrong_type_argument (Qplistp, Flist (nargs, args)); | ||
| 518 | |||
| 519 | /* Start from the back so keyword values appearing | ||
| 520 | first take precedence. */ | ||
| 521 | for (ptrdiff_t i = nargs; i > 0; i -= 2) { | ||
| 522 | Lisp_Object key = args[i - 2]; | ||
| 523 | Lisp_Object value = args[i - 1]; | ||
| 524 | if (configure_object_type && EQ (key, QCobject_type)) | ||
| 525 | { | ||
| 526 | if (EQ (value, Qhash_table)) | ||
| 527 | conf->object_type = json_object_hashtable; | ||
| 528 | else if (EQ (value, Qalist)) | ||
| 529 | conf->object_type = json_object_alist; | ||
| 530 | else if (EQ (value, Qplist)) | ||
| 531 | conf->object_type = json_object_plist; | ||
| 532 | else | ||
| 533 | wrong_choice (list3 (Qhash_table, Qalist, Qplist), value); | ||
| 534 | } | ||
| 535 | else if (EQ (key, QCnull_object)) | ||
| 536 | conf->null_object = value; | ||
| 537 | else if (EQ (key, QCfalse_object)) | ||
| 538 | conf->false_object = value; | ||
| 539 | else if (configure_object_type) | ||
| 540 | wrong_choice (list3 (QCobject_type, | ||
| 541 | QCnull_object, | ||
| 542 | QCfalse_object), | ||
| 543 | value); | ||
| 544 | else | ||
| 545 | wrong_choice (list2 (QCnull_object, | ||
| 546 | QCfalse_object), | ||
| 547 | value); | ||
| 548 | } | ||
| 549 | } | ||
| 550 | |||
| 551 | DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, MANY, | ||
| 552 | NULL, | ||
| 495 | doc: /* Return the JSON representation of OBJECT as a string. | 553 | doc: /* Return the JSON representation of OBJECT as a string. |
| 554 | |||
| 496 | OBJECT must be a vector, hashtable, alist, or plist and its elements | 555 | OBJECT must be a vector, hashtable, alist, or plist and its elements |
| 497 | can recursively contain `:null', `:false', t, numbers, strings, or | 556 | can recursively contain the Lisp equivalents to the JSON null and |
| 498 | other vectors hashtables, alists or plists. `:null', `:false', and t | 557 | false values, t, numbers, strings, or other vectors hashtables, alists |
| 499 | will be converted to JSON null, false, and true values, respectively. | 558 | or plists. t will be converted to the JSON true value. Vectors will |
| 500 | Vectors will be converted to JSON arrays, whereas hashtables, alists | 559 | be converted to JSON arrays, whereas hashtables, alists and plists are |
| 501 | and plists are converted to JSON objects. Hashtable keys must be | 560 | converted to JSON objects. Hashtable keys must be strings without |
| 502 | strings without embedded null characters and must be unique within | 561 | embedded null characters and must be unique within each object. Alist |
| 503 | each object. Alist and plist keys must be symbols; if a key is | 562 | and plist keys must be symbols; if a key is duplicate, the first |
| 504 | duplicate, the first instance is used. */) | 563 | instance is used. |
| 505 | (Lisp_Object object) | 564 | |
| 565 | The Lisp equivalents to the JSON null and false values are | ||
| 566 | configurable in the arguments ARGS, a list of keyword/argument pairs: | ||
| 567 | |||
| 568 | The keyword argument `:null-object' specifies which object to use | ||
| 569 | to represent a JSON null value. It defaults to `:null'. | ||
| 570 | |||
| 571 | The keyword argument `:false-object' specifies which object to use to | ||
| 572 | represent a JSON false value. It defaults to `:false'. | ||
| 573 | |||
| 574 | In you specify the same value for `:null-object' and `:false-object', | ||
| 575 | a potentially ambiguous situation, the JSON output will not contain | ||
| 576 | any JSON false values. | ||
| 577 | usage: (json-serialize STRING &rest ARGS) */) | ||
| 578 | (ptrdiff_t nargs, Lisp_Object *args) | ||
| 506 | { | 579 | { |
| 507 | ptrdiff_t count = SPECPDL_INDEX (); | 580 | ptrdiff_t count = SPECPDL_INDEX (); |
| 508 | 581 | ||
| @@ -521,7 +594,10 @@ duplicate, the first instance is used. */) | |||
| 521 | } | 594 | } |
| 522 | #endif | 595 | #endif |
| 523 | 596 | ||
| 524 | json_t *json = lisp_to_json_toplevel (object); | 597 | struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; |
| 598 | json_parse_args (nargs - 1, args + 1, &conf, false); | ||
| 599 | |||
| 600 | json_t *json = lisp_to_json_toplevel (args[0], &conf); | ||
| 525 | record_unwind_protect_ptr (json_release_object, json); | 601 | record_unwind_protect_ptr (json_release_object, json); |
| 526 | 602 | ||
| 527 | /* If desired, we might want to add the following flags: | 603 | /* If desired, we might want to add the following flags: |
| @@ -577,12 +653,13 @@ json_insert_callback (const char *buffer, size_t size, void *data) | |||
| 577 | return NILP (d->error) ? 0 : -1; | 653 | return NILP (d->error) ? 0 : -1; |
| 578 | } | 654 | } |
| 579 | 655 | ||
| 580 | DEFUN ("json-insert", Fjson_insert, Sjson_insert, 1, 1, NULL, | 656 | DEFUN ("json-insert", Fjson_insert, Sjson_insert, 1, MANY, |
| 657 | NULL, | ||
| 581 | doc: /* Insert the JSON representation of OBJECT before point. | 658 | doc: /* Insert the JSON representation of OBJECT before point. |
| 582 | This is the same as (insert (json-serialize OBJECT)), but potentially | 659 | This is the same as (insert (json-serialize OBJECT)), but potentially |
| 583 | faster. See the function `json-serialize' for allowed values of | 660 | faster. See the function `json-serialize' for allowed values of |
| 584 | OBJECT. */) | 661 | OBJECT. */) |
| 585 | (Lisp_Object object) | 662 | (ptrdiff_t nargs, Lisp_Object *args) |
| 586 | { | 663 | { |
| 587 | ptrdiff_t count = SPECPDL_INDEX (); | 664 | ptrdiff_t count = SPECPDL_INDEX (); |
| 588 | 665 | ||
| @@ -601,7 +678,10 @@ DEFUN ("json-insert", Fjson_insert, Sjson_insert, 1, 1, NULL, | |||
| 601 | } | 678 | } |
| 602 | #endif | 679 | #endif |
| 603 | 680 | ||
| 604 | json_t *json = lisp_to_json (object); | 681 | struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; |
| 682 | json_parse_args (nargs - 1, args + 1, &conf, false); | ||
| 683 | |||
| 684 | json_t *json = lisp_to_json (args[0], &conf); | ||
| 605 | record_unwind_protect_ptr (json_release_object, json); | 685 | record_unwind_protect_ptr (json_release_object, json); |
| 606 | 686 | ||
| 607 | struct json_insert_data data; | 687 | struct json_insert_data data; |
| @@ -620,18 +700,6 @@ DEFUN ("json-insert", Fjson_insert, Sjson_insert, 1, 1, NULL, | |||
| 620 | return unbind_to (count, Qnil); | 700 | return unbind_to (count, Qnil); |
| 621 | } | 701 | } |
| 622 | 702 | ||
| 623 | enum json_object_type { | ||
| 624 | json_object_hashtable, | ||
| 625 | json_object_alist, | ||
| 626 | json_object_plist | ||
| 627 | }; | ||
| 628 | |||
| 629 | struct json_configuration { | ||
| 630 | enum json_object_type object_type; | ||
| 631 | Lisp_Object null_object; | ||
| 632 | Lisp_Object false_object; | ||
| 633 | }; | ||
| 634 | |||
| 635 | /* Convert a JSON object to a Lisp object. */ | 703 | /* Convert a JSON object to a Lisp object. */ |
| 636 | 704 | ||
| 637 | static _GL_ARG_NONNULL ((1)) Lisp_Object | 705 | static _GL_ARG_NONNULL ((1)) Lisp_Object |
| @@ -751,42 +819,6 @@ json_to_lisp (json_t *json, struct json_configuration *conf) | |||
| 751 | emacs_abort (); | 819 | emacs_abort (); |
| 752 | } | 820 | } |
| 753 | 821 | ||
| 754 | static void | ||
| 755 | json_parse_args (ptrdiff_t nargs, | ||
| 756 | Lisp_Object *args, | ||
| 757 | struct json_configuration *conf) | ||
| 758 | { | ||
| 759 | if ((nargs % 2) != 0) | ||
| 760 | wrong_type_argument (Qplistp, Flist (nargs, args)); | ||
| 761 | |||
| 762 | /* Start from the back so keyword values appearing | ||
| 763 | first take precedence. */ | ||
| 764 | for (ptrdiff_t i = nargs; i > 0; i -= 2) { | ||
| 765 | Lisp_Object key = args[i - 2]; | ||
| 766 | Lisp_Object value = args[i - 1]; | ||
| 767 | if (EQ (key, QCobject_type)) | ||
| 768 | { | ||
| 769 | if (EQ (value, Qhash_table)) | ||
| 770 | conf->object_type = json_object_hashtable; | ||
| 771 | else if (EQ (value, Qalist)) | ||
| 772 | conf->object_type = json_object_alist; | ||
| 773 | else if (EQ (value, Qplist)) | ||
| 774 | conf->object_type = json_object_plist; | ||
| 775 | else | ||
| 776 | wrong_choice (list3 (Qhash_table, Qalist, Qplist), value); | ||
| 777 | } | ||
| 778 | else if (EQ (key, QCnull_object)) | ||
| 779 | conf->null_object = value; | ||
| 780 | else if (EQ (key, QCfalse_object)) | ||
| 781 | conf->false_object = value; | ||
| 782 | else | ||
| 783 | wrong_choice (list3 (QCobject_type, | ||
| 784 | QCnull_object, | ||
| 785 | QCfalse_object), | ||
| 786 | value); | ||
| 787 | } | ||
| 788 | } | ||
| 789 | |||
| 790 | DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, MANY, | 822 | DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, MANY, |
| 791 | NULL, | 823 | NULL, |
| 792 | doc: /* Parse the JSON STRING into a Lisp object. | 824 | doc: /* Parse the JSON STRING into a Lisp object. |
| @@ -808,9 +840,8 @@ to represent a JSON null value. It defaults to `:null'. | |||
| 808 | 840 | ||
| 809 | The keyword argument `:false-object' specifies which object to use to | 841 | The keyword argument `:false-object' specifies which object to use to |
| 810 | represent a JSON false value. It defaults to `:false'. | 842 | represent a JSON false value. It defaults to `:false'. |
| 811 | 843 | usage: (json-parse-string STRING &rest ARGS) */) | |
| 812 | usage: (json-parse-string STRING &rest args) */) | 844 | (ptrdiff_t nargs, Lisp_Object *args) |
| 813 | (ptrdiff_t nargs, Lisp_Object *args) | ||
| 814 | { | 845 | { |
| 815 | ptrdiff_t count = SPECPDL_INDEX (); | 846 | ptrdiff_t count = SPECPDL_INDEX (); |
| 816 | 847 | ||
| @@ -833,7 +864,7 @@ usage: (json-parse-string STRING &rest args) */) | |||
| 833 | Lisp_Object encoded = json_encode (string); | 864 | Lisp_Object encoded = json_encode (string); |
| 834 | check_string_without_embedded_nulls (encoded); | 865 | check_string_without_embedded_nulls (encoded); |
| 835 | struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; | 866 | struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; |
| 836 | json_parse_args (nargs - 1, args + 1, &conf); | 867 | json_parse_args (nargs - 1, args + 1, &conf, true); |
| 837 | 868 | ||
| 838 | json_error_t error; | 869 | json_error_t error; |
| 839 | json_t *object = json_loads (SSDATA (encoded), 0, &error); | 870 | json_t *object = json_loads (SSDATA (encoded), 0, &error); |
| @@ -882,7 +913,7 @@ DEFUN ("json-parse-buffer", Fjson_parse_buffer, Sjson_parse_buffer, | |||
| 882 | This is similar to `json-parse-string', which see. Move point after | 913 | This is similar to `json-parse-string', which see. Move point after |
| 883 | the end of the object if parsing was successful. On error, point is | 914 | the end of the object if parsing was successful. On error, point is |
| 884 | not moved. | 915 | not moved. |
| 885 | usage: (json-parse-buffer &rest args) */) | 916 | usage: (json-parse-buffer &rest args) */) |
| 886 | (ptrdiff_t nargs, Lisp_Object *args) | 917 | (ptrdiff_t nargs, Lisp_Object *args) |
| 887 | { | 918 | { |
| 888 | ptrdiff_t count = SPECPDL_INDEX (); | 919 | ptrdiff_t count = SPECPDL_INDEX (); |
| @@ -903,7 +934,7 @@ usage: (json-parse-buffer &rest args) */) | |||
| 903 | #endif | 934 | #endif |
| 904 | 935 | ||
| 905 | struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; | 936 | struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; |
| 906 | json_parse_args (nargs, args, &conf); | 937 | json_parse_args (nargs, args, &conf, true); |
| 907 | 938 | ||
| 908 | ptrdiff_t point = PT_BYTE; | 939 | ptrdiff_t point = PT_BYTE; |
| 909 | struct json_read_buffer_data data = {.point = point}; | 940 | struct json_read_buffer_data data = {.point = point}; |