mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-27 07:41:28 -08:00
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.
This commit is contained in:
parent
2e2f61efa6
commit
3509aaaefe
3 changed files with 116 additions and 36 deletions
|
|
@ -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
|
||||
|
|
|
|||
97
src/json.c
97
src/json.c
|
|
@ -393,18 +393,39 @@ 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);
|
||||
/* In plists, ensure leading ":" in keys is stripped. It
|
||||
will be reconstructed later in `json_to_lisp'.*/
|
||||
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)
|
||||
{
|
||||
|
|
@ -423,7 +444,7 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
|
|||
|
||||
/* Convert LISP to a toplevel JSON object (array or object). Signal
|
||||
an error of type `wrong-type-argument' if LISP is not a vector,
|
||||
hashtable, or alist. */
|
||||
hashtable, alist, or plist. */
|
||||
|
||||
static json_t *
|
||||
lisp_to_json_toplevel (Lisp_Object lisp)
|
||||
|
|
@ -470,20 +491,21 @@ lisp_to_json (Lisp_Object lisp)
|
|||
return json;
|
||||
}
|
||||
|
||||
/* LISP now must be a vector, hashtable, or alist. */
|
||||
/* LISP now must be a vector, hashtable, alist, or plist. */
|
||||
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, 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 +627,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 +715,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 +766,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,14 +780,15 @@ 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)) */)
|
||||
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 +960,7 @@ syms_of_json (void)
|
|||
|
||||
DEFSYM (QCobject_type, ":object-type");
|
||||
DEFSYM (Qalist, "alist");
|
||||
DEFSYM (Qplist, "plist");
|
||||
|
||||
defsubr (&Sjson_serialize);
|
||||
defsubr (&Sjson_insert);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,31 @@
|
|||
(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))
|
||||
|
|
@ -89,7 +113,9 @@
|
|||
(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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue