aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2018-06-02 00:23:38 +0100
committerJoão Távora2018-06-07 12:30:39 +0100
commit3509aaaefe1996ea46b038850629b6d2f7a726fe (patch)
tree651fec98d47d8c77863f6f0b08c9b3c9c6ac2362
parent2e2f61efa66b69fbd12c83bbd5370a4be2374f66 (diff)
downloademacs-3509aaaefe1996ea46b038850629b6d2f7a726fe.tar.gz
emacs-3509aaaefe1996ea46b038850629b6d2f7a726fe.zip
Accept plists when serializing and parsing JSON
* doc/lispref/text.texi (Parsing JSON): Mention plist support. * src/json.c (lisp_to_json_toplevel_1): Serialize plists to json. (Fjson_serialize): Mention plists in docstring. (enum json_object_type): Add json_object_plist. (json_to_lisp): Parse JSON into plists. (json_parse_object_type): Consider plists. (Fjson_parse_string): Mention plists in docstring. (syms_of_json): New Qplist sym_of_json. (lisp_to_json): Update comment. * test/src/json-tests.el (json-serialize/object) (json-parse-string/object): New plist tests.
-rw-r--r--doc/lispref/text.texi25
-rw-r--r--src/json.c97
-rw-r--r--test/src/json-tests.el30
3 files changed, 116 insertions, 36 deletions
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index 2afcd59a706..2c5b5a1b42e 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -5026,16 +5026,18 @@ represented using Lisp vectors.
5026 5026
5027@item 5027@item
5028JSON has only one map type, the object. JSON objects are represented 5028JSON has only one map type, the object. JSON objects are represented
5029using Lisp hashtables or alists. When an alist contains several 5029using Lisp hashtables, alists or plists. When an alist or plist
5030elements with the same key, Emacs uses only the first element for 5030contains several elements with the same key, Emacs uses only the first
5031serialization, in accordance with the behavior of @code{assq}. 5031element for serialization, in accordance with the behavior of
5032@code{assq}.
5032 5033
5033@end itemize 5034@end itemize
5034 5035
5035@noindent 5036@noindent
5036Note that @code{nil} is a valid alist and represents the empty JSON 5037Note that @code{nil}, being both a valid alist and a valid plist,
5037object, @code{@{@}}, not @code{null}, @code{false}, or an empty array, 5038represents @code{@{@}}, the empty JSON object; not @code{null},
5038all of which are different JSON values. 5039@code{false}, or an empty array, all of which are different JSON
5040values.
5039 5041
5040 If some Lisp object can't be represented in JSON, the serialization 5042 If some Lisp object can't be represented in JSON, the serialization
5041functions will signal an error of type @code{wrong-type-argument}. 5043functions will signal an error of type @code{wrong-type-argument}.
@@ -5058,12 +5060,15 @@ The parsing functions will signal the following errors:
5058 Only top-level values (arrays and objects) can be serialized to 5060 Only top-level values (arrays and objects) can be serialized to
5059JSON. The subobjects within these top-level values can be of any 5061JSON. The subobjects within these top-level values can be of any
5060type. Likewise, the parsing functions will only return vectors, 5062type. Likewise, the parsing functions will only return vectors,
5061hashtables, and alists. 5063hashtables, alists, and plists.
5062 5064
5063 The parsing functions accept keyword arguments. Currently only one 5065 The parsing functions accept keyword arguments. Currently only one
5064keyword argument, @code{:object-type}, is recognized; its value can be 5066keyword argument, @code{:object-type}, is recognized; its value
5065either @code{hash-table} to parse JSON objects as hashtables with 5067decides which Lisp object to use for representing the key-value
5066string keys (the default) or @code{alist} to parse them as alists. 5068mappings of a JSON object. It can be either @code{hash-table}, the
5069default, to make hashtables with strings as keys, @code{alist} to use
5070alists with symbols as keys or @code{plist} to use plists with keyword
5071symbols as keys.
5067 5072
5068@defun json-serialize object 5073@defun json-serialize object
5069This function returns a new Lisp string which contains the JSON 5074This function returns a new Lisp string which contains the JSON
diff --git a/src/json.c b/src/json.c
index b046d34f667..afb81587a47 100644
--- a/src/json.c
+++ b/src/json.c
@@ -393,18 +393,39 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
393 *json = json_check (json_object ()); 393 *json = json_check (json_object ());
394 ptrdiff_t count = SPECPDL_INDEX (); 394 ptrdiff_t count = SPECPDL_INDEX ();
395 record_unwind_protect_ptr (json_release_object, *json); 395 record_unwind_protect_ptr (json_release_object, *json);
396 bool is_plist = !CONSP (XCAR (tail));
396 FOR_EACH_TAIL (tail) 397 FOR_EACH_TAIL (tail)
397 { 398 {
398 Lisp_Object pair = XCAR (tail); 399 const char *key_str;
399 CHECK_CONS (pair); 400 Lisp_Object value;
400 Lisp_Object key_symbol = XCAR (pair); 401 Lisp_Object key_symbol;
401 Lisp_Object value = XCDR (pair); 402 if (is_plist)
403 {
404 key_symbol = XCAR (tail);
405 tail = XCDR (tail);
406 CHECK_CONS (tail);
407 value = XCAR (tail);
408 if (EQ (tail, li.tortoise)) circular_list (lisp);
409 }
410 else
411 {
412 Lisp_Object pair = XCAR (tail);
413 CHECK_CONS (pair);
414 key_symbol = XCAR (pair);
415 value = XCDR (pair);
416 }
402 CHECK_SYMBOL (key_symbol); 417 CHECK_SYMBOL (key_symbol);
403 Lisp_Object key = SYMBOL_NAME (key_symbol); 418 Lisp_Object key = SYMBOL_NAME (key_symbol);
404 /* We can't specify the length, so the string must be 419 /* We can't specify the length, so the string must be
405 null-terminated. */ 420 null-terminated. */
406 check_string_without_embedded_nulls (key); 421 check_string_without_embedded_nulls (key);
407 const char *key_str = SSDATA (key); 422 key_str = SSDATA (key);
423 /* In plists, ensure leading ":" in keys is stripped. It
424 will be reconstructed later in `json_to_lisp'.*/
425 if (is_plist && ':' == key_str[0] && key_str[1])
426 {
427 key_str = &key_str[1];
428 }
408 /* Only add element if key is not already present. */ 429 /* Only add element if key is not already present. */
409 if (json_object_get (*json, key_str) == NULL) 430 if (json_object_get (*json, key_str) == NULL)
410 { 431 {
@@ -423,7 +444,7 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
423 444
424/* Convert LISP to a toplevel JSON object (array or object). Signal 445/* Convert LISP to a toplevel JSON object (array or object). Signal
425 an error of type `wrong-type-argument' if LISP is not a vector, 446 an error of type `wrong-type-argument' if LISP is not a vector,
426 hashtable, or alist. */ 447 hashtable, alist, or plist. */
427 448
428static json_t * 449static json_t *
429lisp_to_json_toplevel (Lisp_Object lisp) 450lisp_to_json_toplevel (Lisp_Object lisp)
@@ -470,20 +491,21 @@ lisp_to_json (Lisp_Object lisp)
470 return json; 491 return json;
471 } 492 }
472 493
473 /* LISP now must be a vector, hashtable, or alist. */ 494 /* LISP now must be a vector, hashtable, alist, or plist. */
474 return lisp_to_json_toplevel (lisp); 495 return lisp_to_json_toplevel (lisp);
475} 496}
476 497
477DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, 1, NULL, 498DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, 1, NULL,
478 doc: /* Return the JSON representation of OBJECT as a string. 499 doc: /* Return the JSON representation of OBJECT as a string.
479OBJECT must be a vector, hashtable, or alist, and its elements can 500OBJECT must be a vector, hashtable, alist, or plist and its elements
480recursively contain `:null', `:false', t, numbers, strings, or other 501can recursively contain `:null', `:false', t, numbers, strings, or
481vectors hashtables, and alist. `:null', `:false', and t will be 502other vectors hashtables, alists or plists. `:null', `:false', and t
482converted to JSON null, false, and true values, respectively. Vectors 503will be converted to JSON null, false, and true values, respectively.
483will be converted to JSON arrays, and hashtables and alists to JSON 504Vectors will be converted to JSON arrays, whereas hashtables, alists
484objects. Hashtable keys must be strings without embedded null 505and plists are converted to JSON objects. Hashtable keys must be
485characters and must be unique within each object. Alist keys must be 506strings without embedded null characters and must be unique within
486symbols; if a key is duplicate, the first instance is used. */) 507each object. Alist and plist keys must be symbols; if a key is
508duplicate, the first instance is used. */)
487 (Lisp_Object object) 509 (Lisp_Object object)
488{ 510{
489 ptrdiff_t count = SPECPDL_INDEX (); 511 ptrdiff_t count = SPECPDL_INDEX ();
@@ -605,6 +627,7 @@ OBJECT. */)
605enum json_object_type { 627enum json_object_type {
606 json_object_hashtable, 628 json_object_hashtable,
607 json_object_alist, 629 json_object_alist,
630 json_object_plist
608}; 631};
609 632
610/* Convert a JSON object to a Lisp object. */ 633/* Convert a JSON object to a Lisp object. */
@@ -692,6 +715,28 @@ json_to_lisp (json_t *json, enum json_object_type object_type)
692 result = Fnreverse (result); 715 result = Fnreverse (result);
693 break; 716 break;
694 } 717 }
718 case json_object_plist:
719 {
720 result = Qnil;
721 const char *key_str;
722 json_t *value;
723 json_object_foreach (json, key_str, value)
724 {
725 USE_SAFE_ALLOCA;
726 ptrdiff_t key_str_len = strlen (key_str);
727 char *keyword_key_str = SAFE_ALLOCA (1 + key_str_len + 1);
728 keyword_key_str[0] = ':';
729 strcpy (&keyword_key_str[1], key_str);
730 Lisp_Object key = intern_1 (keyword_key_str, key_str_len + 1);
731 /* Build the plist as value-key since we're going to
732 reverse it in the end.*/
733 result = Fcons (key, result);
734 result = Fcons (json_to_lisp (value, object_type), result);
735 SAFE_FREE ();
736 }
737 result = Fnreverse (result);
738 break;
739 }
695 default: 740 default:
696 /* Can't get here. */ 741 /* Can't get here. */
697 emacs_abort (); 742 emacs_abort ();
@@ -721,8 +766,10 @@ json_parse_object_type (ptrdiff_t nargs, Lisp_Object *args)
721 return json_object_hashtable; 766 return json_object_hashtable;
722 else if (EQ (value, Qalist)) 767 else if (EQ (value, Qalist))
723 return json_object_alist; 768 return json_object_alist;
769 else if (EQ (value, Qplist))
770 return json_object_plist;
724 else 771 else
725 wrong_choice (list2 (Qhash_table, Qalist), value); 772 wrong_choice (list3 (Qhash_table, Qalist, Qplist), value);
726 } 773 }
727 default: 774 default:
728 wrong_type_argument (Qplistp, Flist (nargs, args)); 775 wrong_type_argument (Qplistp, Flist (nargs, args));
@@ -733,14 +780,15 @@ DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, MANY,
733 NULL, 780 NULL,
734 doc: /* Parse the JSON STRING into a Lisp object. 781 doc: /* Parse the JSON STRING into a Lisp object.
735This is essentially the reverse operation of `json-serialize', which 782This is essentially the reverse operation of `json-serialize', which
736see. The returned object will be a vector, hashtable, or alist. Its 783see. The returned object will be a vector, hashtable, alist, or
737elements will be `:null', `:false', t, numbers, strings, or further 784plist. Its elements will be `:null', `:false', t, numbers, strings,
738vectors, hashtables, and alists. If there are duplicate keys in an 785or further vectors, hashtables, alists, or plists. If there are
739object, all but the last one are ignored. If STRING doesn't contain a 786duplicate keys in an object, all but the last one are ignored. If
740valid JSON object, an error of type `json-parse-error' is signaled. 787STRING doesn't contain a valid JSON object, an error of type
741The keyword argument `:object-type' specifies which Lisp type is used 788`json-parse-error' is signaled. The keyword argument `:object-type'
742to represent objects; it can be `hash-table' or `alist'. 789specifies which Lisp type is used to represent objects; it can be
743usage: (json-parse-string STRING &key (OBJECT-TYPE \\='hash-table)) */) 790`hash-table', `alist' or `plist'.
791usage: (json-parse-string STRING &key (OBJECT-TYPE \\='hash-table)) */)
744 (ptrdiff_t nargs, Lisp_Object *args) 792 (ptrdiff_t nargs, Lisp_Object *args)
745{ 793{
746 ptrdiff_t count = SPECPDL_INDEX (); 794 ptrdiff_t count = SPECPDL_INDEX ();
@@ -912,6 +960,7 @@ syms_of_json (void)
912 960
913 DEFSYM (QCobject_type, ":object-type"); 961 DEFSYM (QCobject_type, ":object-type");
914 DEFSYM (Qalist, "alist"); 962 DEFSYM (Qalist, "alist");
963 DEFSYM (Qplist, "plist");
915 964
916 defsubr (&Sjson_serialize); 965 defsubr (&Sjson_serialize);
917 defsubr (&Sjson_insert); 966 defsubr (&Sjson_insert);
diff --git a/test/src/json-tests.el b/test/src/json-tests.el
index 09067bad8c8..7a193545b1a 100644
--- a/test/src/json-tests.el
+++ b/test/src/json-tests.el
@@ -69,7 +69,31 @@
69 (should-error (json-serialize '((1 . 2))) :type 'wrong-type-argument) 69 (should-error (json-serialize '((1 . 2))) :type 'wrong-type-argument)
70 (should-error (json-serialize '((a . 1) . b)) :type 'wrong-type-argument) 70 (should-error (json-serialize '((a . 1) . b)) :type 'wrong-type-argument)
71 (should-error (json-serialize '#1=((a . 1) . #1#)) :type 'circular-list) 71 (should-error (json-serialize '#1=((a . 1) . #1#)) :type 'circular-list)
72 (should-error (json-serialize '(#1=(a #1#))))) 72 (should-error (json-serialize '(#1=(a #1#))))
73
74 (should (equal (json-serialize '(:abc [1 2 t] :def :null))
75 "{\"abc\":[1,2,true],\"def\":null}"))
76 (should (equal (json-serialize '(abc [1 2 t] :def :null))
77 "{\"abc\":[1,2,true],\"def\":null}"))
78 (should-error (json-serialize '#1=(:a 1 . #1#)) :type 'circular-list)
79 (should-error (json-serialize '#1=(:a 1 :b . #1#)) :type 'circular-list)
80 (should-error (json-serialize '(:foo "bar" (unexpected-alist-key . 1)))
81 :type 'wrong-type-argument)
82 (should-error (json-serialize '((abc . "abc") :unexpected-plist-key "key"))
83 :type 'wrong-type-argument)
84 (should-error (json-serialize '(:foo bar :odd-numbered))
85 :type 'wrong-type-argument)
86 (should (equal
87 (json-serialize
88 (list :detect-hash-table #s(hash-table test equal data ("bla" "ble"))
89 :detect-alist `((bla . "ble"))
90 :detect-plist `(:bla "ble")))
91 "\
92{\
93\"detect-hash-table\":{\"bla\":\"ble\"},\
94\"detect-alist\":{\"bla\":\"ble\"},\
95\"detect-plist\":{\"bla\":\"ble\"}\
96}")))
73 97
74(ert-deftest json-serialize/object-with-duplicate-keys () 98(ert-deftest json-serialize/object-with-duplicate-keys ()
75 (skip-unless (fboundp 'json-serialize)) 99 (skip-unless (fboundp 'json-serialize))
@@ -89,7 +113,9 @@
89 (should (equal (cl-sort (map-pairs actual) #'string< :key #'car) 113 (should (equal (cl-sort (map-pairs actual) #'string< :key #'car)
90 '(("abc" . [9 :false]) ("def" . :null))))) 114 '(("abc" . [9 :false]) ("def" . :null)))))
91 (should (equal (json-parse-string input :object-type 'alist) 115 (should (equal (json-parse-string input :object-type 'alist)
92 '((abc . [9 :false]) (def . :null)))))) 116 '((abc . [9 :false]) (def . :null))))
117 (should (equal (json-parse-string input :object-type 'plist)
118 '(:abc [9 :false] :def :null)))))
93 119
94(ert-deftest json-parse-string/string () 120(ert-deftest json-parse-string/string ()
95 (skip-unless (fboundp 'json-parse-string)) 121 (skip-unless (fboundp 'json-parse-string))