aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Stephani2017-12-13 23:35:07 +0100
committerPhilipp Stephani2017-12-19 18:20:55 +0100
commitdb4f12e93f466832a5e5e1d512aff87ea90ef197 (patch)
tree585a95c4cfb8cf765e8df7dc0fe623ed786b6444
parent16813e6faa32b1741685ee429132251846d253a3 (diff)
downloademacs-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.texi20
-rw-r--r--src/json.c129
-rw-r--r--test/src/json-tests.el16
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
4967JSON has only one map type, the object. JSON objects are represented 4967JSON has only one map type, the object. JSON objects are represented
4968using Lisp hashtables. 4968using Lisp hashtables or alists.
4969 4969
4970@end itemize 4970@end itemize
4971 4971
4972@noindent 4972@noindent
4973Note that @code{nil} doesn't represent any JSON values: this is to 4973Note that @code{nil} represents the empty JSON object, @code{@{@}},
4974avoid confusion, because @code{nil} could either represent 4974not @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
4976different JSON values. 4975different 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
4997JSON. The subobjects within these top-level values can be of any 4996JSON. The subobjects within these top-level values can be of any
4998type. Likewise, the parsing functions will only return vectors and 4997type. Likewise, the parsing functions will only return vectors,
4999hashtables. 4998hashtables, and alists.
4999
5000 The parsing functions accept keyword arguments. Currently only one
5001keyword argument, @code{:object-type}, is recognized; its value can be
5002either @code{hash-table} to parse JSON objects as hashtables with
5003string keys (the default) or @code{alist} to parse them as alists.
5000 5004
5001@defun json-serialize object 5005@defun json-serialize object
5002This function returns a new Lisp string which contains the JSON 5006This 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
5008current buffer before point. 5012current 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})
5012This function parses the JSON value in @var{string}, which must be a 5016This function parses the JSON value in @var{string}, which must be a
5013Lisp string. 5017Lisp 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})
5017This function reads the next JSON value from the current buffer, 5021This function reads the next JSON value from the current buffer,
5018starting at point. It moves point to the position immediately after 5022starting at point. It moves point to the position immediately after
5019the value if a value could be read and converted to Lisp; otherwise it 5023the 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
521enum 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
523static _GL_ARG_NONNULL ((1)) Lisp_Object 528static _GL_ARG_NONNULL ((1)) Lisp_Object
524json_to_lisp (json_t *json) 529json_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
592DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, 1, NULL, 623static enum json_object_type
624json_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
648DEFUN ("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.
594This is essentially the reverse operation of `json-serialize', which 651This is essentially the reverse operation of `json-serialize', which
595see. The returned object will be a vector or hashtable. Its elements 652see. The returned object will be a vector, hashtable, or alist. Its
596will be `:null', `:false', t, numbers, strings, or further vectors and 653elements will be `:null', `:false', t, numbers, strings, or further
597hashtables. If there are duplicate keys in an object, all but the 654vectors, hashtables, and alists. If there are duplicate keys in an
598last one are ignored. If STRING doesn't contain a valid JSON object, 655object, all but the last one are ignored. If STRING doesn't contain a
599an error of type `json-parse-error' is signaled. */) 656valid JSON object, an error of type `json-parse-error' is signaled.
600 (Lisp_Object string) 657The keyword argument `:object-type' specifies which Lisp type is used
658to represent objects; it can be `hash-table' or `alist'.
659usage: (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
634struct json_read_buffer_data 697struct json_read_buffer_data
@@ -661,12 +724,13 @@ json_read_buffer_callback (void *buffer, size_t buflen, void *data)
661} 724}
662 725
663DEFUN ("json-parse-buffer", Fjson_parse_buffer, Sjson_parse_buffer, 726DEFUN ("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.
666This is similar to `json-parse-string', which see. Move point after 729This is similar to `json-parse-string', which see. Move point after
667the end of the object if parsing was successful. On error, point is 730the end of the object if parsing was successful. On error, point is
668not moved. */) 731not moved.
669 (void) 732usage: (&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))