diff options
| author | Philipp Stephani | 2017-12-13 23:35:07 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2017-12-19 18:20:55 +0100 |
| commit | db4f12e93f466832a5e5e1d512aff87ea90ef197 (patch) | |
| tree | 585a95c4cfb8cf765e8df7dc0fe623ed786b6444 | |
| parent | 16813e6faa32b1741685ee429132251846d253a3 (diff) | |
| download | emacs-db4f12e93f466832a5e5e1d512aff87ea90ef197.tar.gz emacs-db4f12e93f466832a5e5e1d512aff87ea90ef197.zip | |
Allow JSON parser functions to return alists
* src/json.c (Fjson_parse_string, Fjson_parse_buffer): Give these
functions a keyword argument to specify the return type for JSON
objects.
(json_to_lisp): Convert objects to alists if requested.
(json_parse_object_type): New helper function to parse keyword
arguments.
* test/src/json-tests.el (json-parse-string/object): Add a unit test.
* doc/lispref/text.texi (Parsing JSON): Document new functionality.
| -rw-r--r-- | doc/lispref/text.texi | 20 | ||||
| -rw-r--r-- | src/json.c | 129 | ||||
| -rw-r--r-- | test/src/json-tests.el | 16 |
3 files changed, 120 insertions, 45 deletions
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index 5b288d9750e..9592702ef1c 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi | |||
| @@ -4965,14 +4965,13 @@ represented using Lisp vectors. | |||
| 4965 | 4965 | ||
| 4966 | @item | 4966 | @item |
| 4967 | JSON has only one map type, the object. JSON objects are represented | 4967 | JSON has only one map type, the object. JSON objects are represented |
| 4968 | using Lisp hashtables. | 4968 | using Lisp hashtables or alists. |
| 4969 | 4969 | ||
| 4970 | @end itemize | 4970 | @end itemize |
| 4971 | 4971 | ||
| 4972 | @noindent | 4972 | @noindent |
| 4973 | Note that @code{nil} doesn't represent any JSON values: this is to | 4973 | Note that @code{nil} represents the empty JSON object, @code{@{@}}, |
| 4974 | avoid confusion, because @code{nil} could either represent | 4974 | not @code{null}, @code{false}, or an empty array, all of which are |
| 4975 | @code{null}, @code{false}, or an empty array, all of which are | ||
| 4976 | different JSON values. | 4975 | different JSON values. |
| 4977 | 4976 | ||
| 4978 | If some Lisp object can't be represented in JSON, the serialization | 4977 | If some Lisp object can't be represented in JSON, the serialization |
| @@ -4995,8 +4994,13 @@ The parsing functions will signal the following errors: | |||
| 4995 | 4994 | ||
| 4996 | Only top-level values (arrays and objects) can be serialized to | 4995 | Only top-level values (arrays and objects) can be serialized to |
| 4997 | JSON. The subobjects within these top-level values can be of any | 4996 | JSON. The subobjects within these top-level values can be of any |
| 4998 | type. Likewise, the parsing functions will only return vectors and | 4997 | type. Likewise, the parsing functions will only return vectors, |
| 4999 | hashtables. | 4998 | hashtables, and alists. |
| 4999 | |||
| 5000 | The parsing functions accept keyword arguments. Currently only one | ||
| 5001 | keyword argument, @code{:object-type}, is recognized; its value can be | ||
| 5002 | either @code{hash-table} to parse JSON objects as hashtables with | ||
| 5003 | string keys (the default) or @code{alist} to parse them as alists. | ||
| 5000 | 5004 | ||
| 5001 | @defun json-serialize object | 5005 | @defun json-serialize object |
| 5002 | This function returns a new Lisp string which contains the JSON | 5006 | This function returns a new Lisp string which contains the JSON |
| @@ -5008,12 +5012,12 @@ This function inserts the JSON representation of @var{object} into the | |||
| 5008 | current buffer before point. | 5012 | current buffer before point. |
| 5009 | @end defun | 5013 | @end defun |
| 5010 | 5014 | ||
| 5011 | @defun json-parse-string string | 5015 | @defun json-parse-string string &key (object-type @code{hash-table}) |
| 5012 | This function parses the JSON value in @var{string}, which must be a | 5016 | This function parses the JSON value in @var{string}, which must be a |
| 5013 | Lisp string. | 5017 | Lisp string. |
| 5014 | @end defun | 5018 | @end defun |
| 5015 | 5019 | ||
| 5016 | @defun json-parse-buffer | 5020 | @defun json-parse-buffer &key (object-type @code{hash-table}) |
| 5017 | This function reads the next JSON value from the current buffer, | 5021 | This function reads the next JSON value from the current buffer, |
| 5018 | starting at point. It moves point to the position immediately after | 5022 | starting at point. It moves point to the position immediately after |
| 5019 | the value if a value could be read and converted to Lisp; otherwise it | 5023 | the value if a value could be read and converted to Lisp; otherwise it |
diff --git a/src/json.c b/src/json.c index 29e4400fc91..47c5b8ff468 100644 --- a/src/json.c +++ b/src/json.c | |||
| @@ -518,10 +518,15 @@ OBJECT. */) | |||
| 518 | return unbind_to (count, Qnil); | 518 | return unbind_to (count, Qnil); |
| 519 | } | 519 | } |
| 520 | 520 | ||
| 521 | enum json_object_type { | ||
| 522 | json_object_hashtable, | ||
| 523 | json_object_alist, | ||
| 524 | }; | ||
| 525 | |||
| 521 | /* Convert a JSON object to a Lisp object. */ | 526 | /* Convert a JSON object to a Lisp object. */ |
| 522 | 527 | ||
| 523 | static _GL_ARG_NONNULL ((1)) Lisp_Object | 528 | static _GL_ARG_NONNULL ((1)) Lisp_Object |
| 524 | json_to_lisp (json_t *json) | 529 | json_to_lisp (json_t *json, enum json_object_type object_type) |
| 525 | { | 530 | { |
| 526 | switch (json_typeof (json)) | 531 | switch (json_typeof (json)) |
| 527 | { | 532 | { |
| @@ -555,7 +560,7 @@ json_to_lisp (json_t *json) | |||
| 555 | Lisp_Object result = Fmake_vector (make_natnum (size), Qunbound); | 560 | Lisp_Object result = Fmake_vector (make_natnum (size), Qunbound); |
| 556 | for (ptrdiff_t i = 0; i < size; ++i) | 561 | for (ptrdiff_t i = 0; i < size; ++i) |
| 557 | ASET (result, i, | 562 | ASET (result, i, |
| 558 | json_to_lisp (json_array_get (json, i))); | 563 | json_to_lisp (json_array_get (json, i), object_type)); |
| 559 | --lisp_eval_depth; | 564 | --lisp_eval_depth; |
| 560 | return result; | 565 | return result; |
| 561 | } | 566 | } |
| @@ -563,23 +568,49 @@ json_to_lisp (json_t *json) | |||
| 563 | { | 568 | { |
| 564 | if (++lisp_eval_depth > max_lisp_eval_depth) | 569 | if (++lisp_eval_depth > max_lisp_eval_depth) |
| 565 | xsignal0 (Qjson_object_too_deep); | 570 | xsignal0 (Qjson_object_too_deep); |
| 566 | size_t size = json_object_size (json); | 571 | Lisp_Object result; |
| 567 | if (FIXNUM_OVERFLOW_P (size)) | 572 | switch (object_type) |
| 568 | xsignal0 (Qoverflow_error); | ||
| 569 | Lisp_Object result = CALLN (Fmake_hash_table, QCtest, Qequal, | ||
| 570 | QCsize, make_natnum (size)); | ||
| 571 | struct Lisp_Hash_Table *h = XHASH_TABLE (result); | ||
| 572 | const char *key_str; | ||
| 573 | json_t *value; | ||
| 574 | json_object_foreach (json, key_str, value) | ||
| 575 | { | 573 | { |
| 576 | Lisp_Object key = json_build_string (key_str); | 574 | case json_object_hashtable: |
| 577 | EMACS_UINT hash; | 575 | { |
| 578 | ptrdiff_t i = hash_lookup (h, key, &hash); | 576 | size_t size = json_object_size (json); |
| 579 | /* Keys in JSON objects are unique, so the key can't be | 577 | if (FIXNUM_OVERFLOW_P (size)) |
| 580 | present yet. */ | 578 | xsignal0 (Qoverflow_error); |
| 581 | eassert (i < 0); | 579 | result = CALLN (Fmake_hash_table, QCtest, Qequal, QCsize, |
| 582 | hash_put (h, key, json_to_lisp (value), hash); | 580 | make_natnum (size)); |
| 581 | struct Lisp_Hash_Table *h = XHASH_TABLE (result); | ||
| 582 | const char *key_str; | ||
| 583 | json_t *value; | ||
| 584 | json_object_foreach (json, key_str, value) | ||
| 585 | { | ||
| 586 | Lisp_Object key = json_build_string (key_str); | ||
| 587 | EMACS_UINT hash; | ||
| 588 | ptrdiff_t i = hash_lookup (h, key, &hash); | ||
| 589 | /* Keys in JSON objects are unique, so the key can't | ||
| 590 | be present yet. */ | ||
| 591 | eassert (i < 0); | ||
| 592 | hash_put (h, key, json_to_lisp (value, object_type), hash); | ||
| 593 | } | ||
| 594 | break; | ||
| 595 | } | ||
| 596 | case json_object_alist: | ||
| 597 | { | ||
| 598 | result = Qnil; | ||
| 599 | const char *key_str; | ||
| 600 | json_t *value; | ||
| 601 | json_object_foreach (json, key_str, value) | ||
| 602 | { | ||
| 603 | Lisp_Object key = Fintern (json_build_string (key_str), Qnil); | ||
| 604 | result | ||
| 605 | = Fcons (Fcons (key, json_to_lisp (value, object_type)), | ||
| 606 | result); | ||
| 607 | } | ||
| 608 | result = Fnreverse (result); | ||
| 609 | break; | ||
| 610 | } | ||
| 611 | default: | ||
| 612 | /* Can't get here. */ | ||
| 613 | emacs_abort (); | ||
| 583 | } | 614 | } |
| 584 | --lisp_eval_depth; | 615 | --lisp_eval_depth; |
| 585 | return result; | 616 | return result; |
| @@ -589,15 +620,44 @@ json_to_lisp (json_t *json) | |||
| 589 | emacs_abort (); | 620 | emacs_abort (); |
| 590 | } | 621 | } |
| 591 | 622 | ||
| 592 | DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, 1, NULL, | 623 | static enum json_object_type |
| 624 | json_parse_object_type (ptrdiff_t nargs, Lisp_Object *args) | ||
| 625 | { | ||
| 626 | switch (nargs) | ||
| 627 | { | ||
| 628 | case 0: | ||
| 629 | return json_object_hashtable; | ||
| 630 | case 2: | ||
| 631 | { | ||
| 632 | Lisp_Object key = args[0]; | ||
| 633 | Lisp_Object value = args[1]; | ||
| 634 | if (!EQ (key, QCobject_type)) | ||
| 635 | wrong_choice (list1 (QCobject_type), key); | ||
| 636 | if (EQ (value, Qhash_table)) | ||
| 637 | return json_object_hashtable; | ||
| 638 | else if (EQ (value, Qalist)) | ||
| 639 | return json_object_alist; | ||
| 640 | else | ||
| 641 | wrong_choice (list2 (Qhash_table, Qalist), value); | ||
| 642 | } | ||
| 643 | default: | ||
| 644 | wrong_type_argument (Qplistp, Flist (nargs, args)); | ||
| 645 | } | ||
| 646 | } | ||
| 647 | |||
| 648 | DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, MANY, | ||
| 649 | NULL, | ||
| 593 | doc: /* Parse the JSON STRING into a Lisp object. | 650 | doc: /* Parse the JSON STRING into a Lisp object. |
| 594 | This is essentially the reverse operation of `json-serialize', which | 651 | This is essentially the reverse operation of `json-serialize', which |
| 595 | see. The returned object will be a vector or hashtable. Its elements | 652 | see. The returned object will be a vector, hashtable, or alist. Its |
| 596 | will be `:null', `:false', t, numbers, strings, or further vectors and | 653 | elements will be `:null', `:false', t, numbers, strings, or further |
| 597 | hashtables. If there are duplicate keys in an object, all but the | 654 | vectors, hashtables, and alists. If there are duplicate keys in an |
| 598 | last one are ignored. If STRING doesn't contain a valid JSON object, | 655 | object, all but the last one are ignored. If STRING doesn't contain a |
| 599 | an error of type `json-parse-error' is signaled. */) | 656 | valid JSON object, an error of type `json-parse-error' is signaled. |
| 600 | (Lisp_Object string) | 657 | The keyword argument `:object-type' specifies which Lisp type is used |
| 658 | to represent objects; it can be `hash-table' or `alist'. | ||
| 659 | usage: (string &key (OBJECT-TYPE \\='hash-table)) */) | ||
| 660 | (ptrdiff_t nargs, Lisp_Object *args) | ||
| 601 | { | 661 | { |
| 602 | ptrdiff_t count = SPECPDL_INDEX (); | 662 | ptrdiff_t count = SPECPDL_INDEX (); |
| 603 | 663 | ||
| @@ -616,8 +676,11 @@ an error of type `json-parse-error' is signaled. */) | |||
| 616 | } | 676 | } |
| 617 | #endif | 677 | #endif |
| 618 | 678 | ||
| 679 | Lisp_Object string = args[0]; | ||
| 619 | Lisp_Object encoded = json_encode (string); | 680 | Lisp_Object encoded = json_encode (string); |
| 620 | check_string_without_embedded_nulls (encoded); | 681 | check_string_without_embedded_nulls (encoded); |
| 682 | enum json_object_type object_type | ||
| 683 | = json_parse_object_type (nargs - 1, args + 1); | ||
| 621 | 684 | ||
| 622 | json_error_t error; | 685 | json_error_t error; |
| 623 | json_t *object = json_loads (SSDATA (encoded), 0, &error); | 686 | json_t *object = json_loads (SSDATA (encoded), 0, &error); |
| @@ -628,7 +691,7 @@ an error of type `json-parse-error' is signaled. */) | |||
| 628 | if (object != NULL) | 691 | if (object != NULL) |
| 629 | record_unwind_protect_ptr (json_release_object, object); | 692 | record_unwind_protect_ptr (json_release_object, object); |
| 630 | 693 | ||
| 631 | return unbind_to (count, json_to_lisp (object)); | 694 | return unbind_to (count, json_to_lisp (object, object_type)); |
| 632 | } | 695 | } |
| 633 | 696 | ||
| 634 | struct json_read_buffer_data | 697 | struct json_read_buffer_data |
| @@ -661,12 +724,13 @@ json_read_buffer_callback (void *buffer, size_t buflen, void *data) | |||
| 661 | } | 724 | } |
| 662 | 725 | ||
| 663 | DEFUN ("json-parse-buffer", Fjson_parse_buffer, Sjson_parse_buffer, | 726 | DEFUN ("json-parse-buffer", Fjson_parse_buffer, Sjson_parse_buffer, |
| 664 | 0, 0, NULL, | 727 | 0, MANY, NULL, |
| 665 | doc: /* Read JSON object from current buffer starting at point. | 728 | doc: /* Read JSON object from current buffer starting at point. |
| 666 | This is similar to `json-parse-string', which see. Move point after | 729 | This is similar to `json-parse-string', which see. Move point after |
| 667 | the end of the object if parsing was successful. On error, point is | 730 | the end of the object if parsing was successful. On error, point is |
| 668 | not moved. */) | 731 | not moved. |
| 669 | (void) | 732 | usage: (&key (OBJECT-TYPE \\='hash-table)) */) |
| 733 | (ptrdiff_t nargs, Lisp_Object *args) | ||
| 670 | { | 734 | { |
| 671 | ptrdiff_t count = SPECPDL_INDEX (); | 735 | ptrdiff_t count = SPECPDL_INDEX (); |
| 672 | 736 | ||
| @@ -685,6 +749,8 @@ not moved. */) | |||
| 685 | } | 749 | } |
| 686 | #endif | 750 | #endif |
| 687 | 751 | ||
| 752 | enum json_object_type object_type = json_parse_object_type (nargs, args); | ||
| 753 | |||
| 688 | ptrdiff_t point = PT_BYTE; | 754 | ptrdiff_t point = PT_BYTE; |
| 689 | struct json_read_buffer_data data = {.point = point}; | 755 | struct json_read_buffer_data data = {.point = point}; |
| 690 | json_error_t error; | 756 | json_error_t error; |
| @@ -698,7 +764,7 @@ not moved. */) | |||
| 698 | record_unwind_protect_ptr (json_release_object, object); | 764 | record_unwind_protect_ptr (json_release_object, object); |
| 699 | 765 | ||
| 700 | /* Convert and then move point only if everything succeeded. */ | 766 | /* Convert and then move point only if everything succeeded. */ |
| 701 | Lisp_Object lisp = json_to_lisp (object); | 767 | Lisp_Object lisp = json_to_lisp (object, object_type); |
| 702 | 768 | ||
| 703 | /* Adjust point by how much we just read. */ | 769 | /* Adjust point by how much we just read. */ |
| 704 | point += error.position; | 770 | point += error.position; |
| @@ -761,6 +827,9 @@ syms_of_json (void) | |||
| 761 | Fput (Qjson_parse_string, Qpure, Qt); | 827 | Fput (Qjson_parse_string, Qpure, Qt); |
| 762 | Fput (Qjson_parse_string, Qside_effect_free, Qt); | 828 | Fput (Qjson_parse_string, Qside_effect_free, Qt); |
| 763 | 829 | ||
| 830 | DEFSYM (QCobject_type, ":object-type"); | ||
| 831 | DEFSYM (Qalist, "alist"); | ||
| 832 | |||
| 764 | defsubr (&Sjson_serialize); | 833 | defsubr (&Sjson_serialize); |
| 765 | defsubr (&Sjson_insert); | 834 | defsubr (&Sjson_insert); |
| 766 | defsubr (&Sjson_parse_string); | 835 | defsubr (&Sjson_parse_string); |
diff --git a/test/src/json-tests.el b/test/src/json-tests.el index 551f8ac5fe4..100bf7bd39b 100644 --- a/test/src/json-tests.el +++ b/test/src/json-tests.el | |||
| @@ -54,13 +54,15 @@ | |||
| 54 | 54 | ||
| 55 | (ert-deftest json-parse-string/object () | 55 | (ert-deftest json-parse-string/object () |
| 56 | (skip-unless (fboundp 'json-parse-string)) | 56 | (skip-unless (fboundp 'json-parse-string)) |
| 57 | (let ((actual | 57 | (let ((input |
| 58 | (json-parse-string | 58 | "{ \"abc\" : [1, 2, true], \"def\" : null, \"abc\" : [9, false] }\n")) |
| 59 | "{ \"abc\" : [1, 2, true], \"def\" : null, \"abc\" : [9, false] }\n"))) | 59 | (let ((actual (json-parse-string input))) |
| 60 | (should (hash-table-p actual)) | 60 | (should (hash-table-p actual)) |
| 61 | (should (equal (hash-table-count actual) 2)) | 61 | (should (equal (hash-table-count actual) 2)) |
| 62 | (should (equal (cl-sort (map-pairs actual) #'string< :key #'car) | 62 | (should (equal (cl-sort (map-pairs actual) #'string< :key #'car) |
| 63 | '(("abc" . [9 :false]) ("def" . :null)))))) | 63 | '(("abc" . [9 :false]) ("def" . :null))))) |
| 64 | (should (equal (json-parse-string input :object-type 'alist) | ||
| 65 | '((abc . [9 :false]) (def . :null)))))) | ||
| 64 | 66 | ||
| 65 | (ert-deftest json-parse-string/string () | 67 | (ert-deftest json-parse-string/string () |
| 66 | (skip-unless (fboundp 'json-parse-string)) | 68 | (skip-unless (fboundp 'json-parse-string)) |