Accept alists when serializing JSON

* src/json.c (lisp_to_json_toplevel_1): Also accept alists
representing objects.

* src/json.c (Fjson_serialize): Update docstring.

* test/src/json-tests.el (json-serialize/object): Add unit tests for
serializing alists.

* doc/lispref/text.texi (Parsing JSON): Document that serialization
functions accept alists.
This commit is contained in:
Philipp Stephani 2017-12-13 22:41:28 +01:00
parent 3455192777
commit f552a957ad
3 changed files with 66 additions and 15 deletions

View file

@ -4929,14 +4929,16 @@ represented using Lisp vectors.
@item
JSON has only one map type, the object. JSON objects are represented
using Lisp hashtables or alists.
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}.
@end itemize
@noindent
Note that @code{nil} 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} 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.
If some Lisp object can't be represented in JSON, the serialization
functions will signal an error of type @code{wrong-type-argument}.

View file

@ -367,12 +367,48 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
clear_unwind_protect (count);
return unbind_to (count, Qnil);
}
else if (NILP (lisp))
{
*json = json_check (json_object ());
return Qnil;
}
else if (CONSP (lisp))
{
Lisp_Object tail = lisp;
*json = json_check (json_object ());
ptrdiff_t count = SPECPDL_INDEX ();
record_unwind_protect_ptr (json_release_object, *json);
FOR_EACH_TAIL (tail)
{
Lisp_Object pair = XCAR (tail);
CHECK_CONS (pair);
Lisp_Object key_symbol = XCAR (pair);
Lisp_Object 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);
/* Only add element if key is not already present. */
if (json_object_get (*json, key_str) == NULL)
{
int status
= json_object_set_new (*json, key_str, lisp_to_json (value));
if (status == -1)
json_out_of_memory ();
}
}
CHECK_LIST_END (tail, lisp);
clear_unwind_protect (count);
return unbind_to (count, Qnil);
}
wrong_type_argument (Qjson_value_p, lisp);
}
/* Convert LISP to a toplevel JSON object (array or object). Signal
an error of type `wrong-type-argument' if LISP is not a vector or
hashtable. */
an error of type `wrong-type-argument' if LISP is not a vector,
hashtable, or alist. */
static json_t *
lisp_to_json_toplevel (Lisp_Object lisp)
@ -413,19 +449,20 @@ lisp_to_json (Lisp_Object lisp)
return json_check (json_stringn (SSDATA (encoded), SBYTES (encoded)));
}
/* LISP now must be a vector or hashtable. */
/* LISP now must be a vector, hashtable, or alist. */
return lisp_to_json_toplevel (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 or hashtable, and its elements can recursively
contain `:null', `:false', t, numbers, strings, or other vectors and
hashtables. `:null', `:false', and t will be converted to JSON null,
false, and true values, respectively. Vectors will be converted to
JSON arrays, and hashtables to JSON objects. Hashtable keys must be
strings without embedded null characters and must be unique within
each object. */)
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. */)
(Lisp_Object object)
{
ptrdiff_t count = SPECPDL_INDEX ();

View file

@ -50,7 +50,19 @@
(puthash "abc" [1 2 t] table)
(puthash "def" :null table)
(should (equal (json-serialize table)
"{\"abc\":[1,2,true],\"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 (equal (json-serialize nil) "{}"))
(should (equal (json-serialize '((abc))) "{\"abc\":{}}"))
(should (equal (json-serialize '((a . 1) (b . 2) (a . 3)))
"{\"a\":1,\"b\":2}"))
(should-error (json-serialize '(abc)) :type 'wrong-type-argument)
(should-error (json-serialize '((a 1))) :type 'wrong-type-argument)
(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#)))))
(ert-deftest json-serialize/object-with-duplicate-keys ()
(skip-unless (fboundp 'json-serialize))