]> git.eshelyaron.com Git - emacs.git/commitdiff
Accept plists when serializing and parsing JSON scratch/support-plists-in-jsonc-autodetect
authorJoão Távora <joaotavora@gmail.com>
Fri, 1 Jun 2018 23:23:38 +0000 (00:23 +0100)
committerJoão Távora <joaotavora@gmail.com>
Sun, 3 Jun 2018 23:45:55 +0000 (00:45 +0100)
* 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

* test/src/json-tests.el (json-serialize/object)
(json-parse-string/object): New plist tests.

doc/lispref/text.texi
src/json.c
test/src/json-tests.el

index 2afcd59a7063226b0129da99bad85ad9e7357959..2c5b5a1b42ee762dc095c460bcd1c14adcd8fe39 100644 (file)
@@ -5026,16 +5026,18 @@ represented using Lisp vectors.
 
 @item
 JSON has only one map type, the object.  JSON objects are represented
-using Lisp hashtables or alists.  When an alist contains several
-elements with the same key, Emacs uses only the first element for
-serialization, in accordance with the behavior of @code{assq}.
+using Lisp hashtables, alists or plists.  When an alist or plist
+contains several elements with the same key, Emacs uses only the first
+element for serialization, in accordance with the behavior of
+@code{assq}.
 
 @end itemize
 
 @noindent
-Note that @code{nil} is a valid alist and represents the empty JSON
-object, @code{@{@}}, not @code{null}, @code{false}, or an empty array,
-all of which are different JSON values.
+Note that @code{nil}, being both a valid alist and a valid plist,
+represents @code{@{@}}, the empty JSON object; not @code{null},
+@code{false}, or an empty array, all of which are different JSON
+values.
 
   If some Lisp object can't be represented in JSON, the serialization
 functions will signal an error of type @code{wrong-type-argument}.
@@ -5058,12 +5060,15 @@ The parsing functions will signal the following errors:
   Only top-level values (arrays and objects) can be serialized to
 JSON.  The subobjects within these top-level values can be of any
 type.  Likewise, the parsing functions will only return vectors,
-hashtables, and alists.
+hashtables, alists, and plists.
 
   The parsing functions accept keyword arguments.  Currently only one
-keyword argument, @code{:object-type}, is recognized; its value can be
-either @code{hash-table} to parse JSON objects as hashtables with
-string keys (the default) or @code{alist} to parse them as alists.
+keyword argument, @code{:object-type}, is recognized; its value
+decides which Lisp object to use for representing the key-value
+mappings of a JSON object.  It can be either @code{hash-table}, the
+default, to make hashtables with strings as keys, @code{alist} to use
+alists with symbols as keys or @code{plist} to use plists with keyword
+symbols as keys.
 
 @defun json-serialize object
 This function returns a new Lisp string which contains the JSON
index b046d34f667fa51cce09b65ea7eaee8808c9d4ba..5ff45aeb2ca7183bd2544fe4ebed042d0a9d77d7 100644 (file)
@@ -393,18 +393,37 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
       *json = json_check (json_object ());
       ptrdiff_t count = SPECPDL_INDEX ();
       record_unwind_protect_ptr (json_release_object, *json);
+      bool is_plist=!CONSP(XCAR (tail));
       FOR_EACH_TAIL (tail)
         {
-          Lisp_Object pair = XCAR (tail);
-          CHECK_CONS (pair);
-          Lisp_Object key_symbol = XCAR (pair);
-          Lisp_Object value = XCDR (pair);
+          const char *key_str;
+          Lisp_Object value;
+          Lisp_Object key_symbol;
+          if (is_plist)
+            {
+              key_symbol = XCAR (tail);
+              tail = XCDR(tail);
+              CHECK_CONS (tail);
+              value = XCAR (tail);
+              if (EQ (tail, li.tortoise)) circular_list (lisp);
+            }
+          else
+            {
+              Lisp_Object pair = XCAR (tail);
+              CHECK_CONS (pair);
+              key_symbol = XCAR (pair);
+              value = XCDR (pair);
+            }
           CHECK_SYMBOL (key_symbol);
           Lisp_Object key = SYMBOL_NAME (key_symbol);
           /* We can't specify the length, so the string must be
              null-terminated.  */
           check_string_without_embedded_nulls (key);
-          const char *key_str = SSDATA (key);
+          key_str = SSDATA (key);
+          /* If using plists, maybe strip the ":" from symbol-name */
+          if (is_plist &&
+              ':' == key_str[0] &&
+              key_str[1] ) key_str = &key_str[1];
           /* Only add element if key is not already present.  */
           if (json_object_get (*json, key_str) == NULL)
             {
@@ -476,14 +495,15 @@ lisp_to_json (Lisp_Object lisp)
 
 DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, 1, NULL,
        doc: /* Return the JSON representation of OBJECT as a string.
-OBJECT must be a vector, hashtable, or alist, and its elements can
-recursively contain `:null', `:false', t, numbers, strings, or other
-vectors hashtables, and alist.  `:null', `:false', and t will be
-converted to JSON null, false, and true values, respectively.  Vectors
-will be converted to JSON arrays, and hashtables and alists to JSON
-objects.  Hashtable keys must be strings without embedded null
-characters and must be unique within each object.  Alist keys must be
-symbols; if a key is duplicate, the first instance is used.  */)
+OBJECT must be a vector, hashtable, alist, or plist and its elements
+can recursively contain `:null', `:false', t, numbers, strings, or
+other vectors hashtables, alists or plists.  `:null', `:false', and t
+will be converted to JSON null, false, and true values, respectively.
+Vectors will be converted to JSON arrays, whereas hashtables, alists
+and plists are converted to JSON objects.  Hashtable keys must be
+strings without embedded null characters and must be unique within
+each object.  Alist and plist keys must be symbols; if a key is
+duplicate, the first instance is used.  */)
   (Lisp_Object object)
 {
   ptrdiff_t count = SPECPDL_INDEX ();
@@ -605,6 +625,7 @@ OBJECT.  */)
 enum json_object_type {
   json_object_hashtable,
   json_object_alist,
+  json_object_plist
 };
 
 /* Convert a JSON object to a Lisp object.  */
@@ -692,6 +713,28 @@ json_to_lisp (json_t *json, enum json_object_type object_type)
               result = Fnreverse (result);
               break;
             }
+          case json_object_plist:
+            {
+              result = Qnil;
+              const char *key_str;
+              json_t *value;
+              json_object_foreach (json, key_str, value)
+                {
+                  USE_SAFE_ALLOCA;
+                  ptrdiff_t key_str_len=strlen (key_str);
+                  char *keyword_key_str = SAFE_ALLOCA (1 + key_str_len + 1);
+                  keyword_key_str[0]=':';
+                  strcpy (&keyword_key_str[1],key_str);
+                  Lisp_Object key = intern_1 (keyword_key_str, key_str_len + 1);
+                  /* Build the plist as value-key since we're going to
+                     reverse it in the end.*/
+                  result = Fcons (key, result);
+                  result = Fcons (json_to_lisp (value, object_type), result);
+                  SAFE_FREE ();
+                }
+              result = Fnreverse (result);
+              break;
+            }
           default:
             /* Can't get here.  */
             emacs_abort ();
@@ -721,8 +764,10 @@ json_parse_object_type (ptrdiff_t nargs, Lisp_Object *args)
           return json_object_hashtable;
         else if (EQ (value, Qalist))
           return json_object_alist;
+        else if (EQ (value, Qplist))
+          return json_object_plist;
         else
-          wrong_choice (list2 (Qhash_table, Qalist), value);
+          wrong_choice (list3 (Qhash_table, Qalist, Qplist), value);
       }
     default:
       wrong_type_argument (Qplistp, Flist (nargs, args));
@@ -733,15 +778,16 @@ DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, MANY,
        NULL,
        doc: /* Parse the JSON STRING into a Lisp object.
 This is essentially the reverse operation of `json-serialize', which
-see.  The returned object will be a vector, hashtable, or alist.  Its
-elements will be `:null', `:false', t, numbers, strings, or further
-vectors, hashtables, and alists.  If there are duplicate keys in an
-object, all but the last one are ignored.  If STRING doesn't contain a
-valid JSON object, an error of type `json-parse-error' is signaled.
-The keyword argument `:object-type' specifies which Lisp type is used
-to represent objects; it can be `hash-table' or `alist'.
-usage: (json-parse-string STRING &key (OBJECT-TYPE \\='hash-table))  */)
-  (ptrdiff_t nargs, Lisp_Object *args)
+see.  The returned object will be a vector, hashtable, alist, or
+plist.  Its elements will be `:null', `:false', t, numbers, strings,
+or further vectors, hashtables, alists, or plists.  If there are
+duplicate keys in an object, all but the last one are ignored.  If
+STRING doesn't contain a valid JSON object, an error of type
+`json-parse-error' is signaled.  The keyword argument `:object-type'
+specifies which Lisp type is used to represent objects; it can be
+`hash-table', `alist' or `plist'.
+usage: (json-parse-string STRING &key (OBJECT-TYPE \\='hash-table)) */)
+     (ptrdiff_t nargs, Lisp_Object *args)
 {
   ptrdiff_t count = SPECPDL_INDEX ();
 
@@ -912,6 +958,7 @@ syms_of_json (void)
 
   DEFSYM (QCobject_type, ":object-type");
   DEFSYM (Qalist, "alist");
+  DEFSYM (Qplist, "plist");
 
   defsubr (&Sjson_serialize);
   defsubr (&Sjson_insert);
index 09067bad8c884ef1e5f5004458157ed26cd297f7..7a193545b1a8ed84395e5db108dfb6f32e45bce3 100644 (file)
   (should-error (json-serialize '((1 . 2))) :type 'wrong-type-argument)
   (should-error (json-serialize '((a . 1) . b)) :type 'wrong-type-argument)
   (should-error (json-serialize '#1=((a . 1) . #1#)) :type 'circular-list)
-  (should-error (json-serialize '(#1=(a #1#)))))
+  (should-error (json-serialize '(#1=(a #1#))))
+
+  (should (equal (json-serialize '(:abc [1 2 t] :def :null))
+                 "{\"abc\":[1,2,true],\"def\":null}"))
+  (should (equal (json-serialize '(abc [1 2 t] :def :null))
+                 "{\"abc\":[1,2,true],\"def\":null}"))
+  (should-error (json-serialize '#1=(:a 1 . #1#)) :type 'circular-list)
+  (should-error (json-serialize '#1=(:a 1 :b . #1#)) :type 'circular-list)
+  (should-error (json-serialize '(:foo "bar" (unexpected-alist-key . 1)))
+                :type 'wrong-type-argument)
+  (should-error (json-serialize '((abc . "abc") :unexpected-plist-key "key"))
+                :type 'wrong-type-argument)
+  (should-error (json-serialize '(:foo bar :odd-numbered))
+                :type 'wrong-type-argument)
+  (should (equal
+           (json-serialize
+            (list :detect-hash-table #s(hash-table test equal data ("bla" "ble"))
+                  :detect-alist `((bla . "ble"))
+                  :detect-plist `(:bla "ble")))
+           "\
+{\
+\"detect-hash-table\":{\"bla\":\"ble\"},\
+\"detect-alist\":{\"bla\":\"ble\"},\
+\"detect-plist\":{\"bla\":\"ble\"}\
+}")))
 
 (ert-deftest json-serialize/object-with-duplicate-keys ()
   (skip-unless (fboundp 'json-serialize))
       (should (equal (cl-sort (map-pairs actual) #'string< :key #'car)
                      '(("abc" . [9 :false]) ("def" . :null)))))
     (should (equal (json-parse-string input :object-type 'alist)
-                   '((abc . [9 :false]) (def . :null))))))
+                   '((abc . [9 :false]) (def . :null))))
+    (should (equal (json-parse-string input :object-type 'plist)
+                   '(:abc [9 :false] :def :null)))))
 
 (ert-deftest json-parse-string/string ()
   (skip-unless (fboundp 'json-parse-string))