diff options
| author | Philipp Stephani | 2017-12-13 22:41:28 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2017-12-24 13:59:25 +0100 |
| commit | f552a957ada23a7ff182fc1ab94221ced3ed1713 (patch) | |
| tree | 359c39cfc24e3c166984717f73884f6c221d1378 | |
| parent | 3455192777459a08a38b0adb311a76202e29f48d (diff) | |
| download | emacs-f552a957ada23a7ff182fc1ab94221ced3ed1713.tar.gz emacs-f552a957ada23a7ff182fc1ab94221ced3ed1713.zip | |
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.
| -rw-r--r-- | doc/lispref/text.texi | 10 | ||||
| -rw-r--r-- | src/json.c | 57 | ||||
| -rw-r--r-- | test/src/json-tests.el | 14 |
3 files changed, 66 insertions, 15 deletions
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index 561ad804344..7a1983641fd 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi | |||
| @@ -4929,14 +4929,16 @@ represented using Lisp vectors. | |||
| 4929 | 4929 | ||
| 4930 | @item | 4930 | @item |
| 4931 | JSON has only one map type, the object. JSON objects are represented | 4931 | JSON has only one map type, the object. JSON objects are represented |
| 4932 | using Lisp hashtables or alists. | 4932 | using Lisp hashtables or alists. When an alist contains several |
| 4933 | elements with the same key, Emacs uses only the first element for | ||
| 4934 | serialization, in accordance with the behavior of @code{assq}. | ||
| 4933 | 4935 | ||
| 4934 | @end itemize | 4936 | @end itemize |
| 4935 | 4937 | ||
| 4936 | @noindent | 4938 | @noindent |
| 4937 | Note that @code{nil} represents the empty JSON object, @code{@{@}}, | 4939 | Note that @code{nil} is a valid alist and represents the empty JSON |
| 4938 | not @code{null}, @code{false}, or an empty array, all of which are | 4940 | object, @code{@{@}}, not @code{null}, @code{false}, or an empty array, |
| 4939 | different JSON values. | 4941 | all of which are different JSON values. |
| 4940 | 4942 | ||
| 4941 | If some Lisp object can't be represented in JSON, the serialization | 4943 | If some Lisp object can't be represented in JSON, the serialization |
| 4942 | functions will signal an error of type @code{wrong-type-argument}. | 4944 | functions will signal an error of type @code{wrong-type-argument}. |
diff --git a/src/json.c b/src/json.c index c1daba199c3..f615c4269f1 100644 --- a/src/json.c +++ b/src/json.c | |||
| @@ -367,12 +367,48 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json) | |||
| 367 | clear_unwind_protect (count); | 367 | clear_unwind_protect (count); |
| 368 | return unbind_to (count, Qnil); | 368 | return unbind_to (count, Qnil); |
| 369 | } | 369 | } |
| 370 | else if (NILP (lisp)) | ||
| 371 | { | ||
| 372 | *json = json_check (json_object ()); | ||
| 373 | return Qnil; | ||
| 374 | } | ||
| 375 | else if (CONSP (lisp)) | ||
| 376 | { | ||
| 377 | Lisp_Object tail = lisp; | ||
| 378 | *json = json_check (json_object ()); | ||
| 379 | ptrdiff_t count = SPECPDL_INDEX (); | ||
| 380 | record_unwind_protect_ptr (json_release_object, *json); | ||
| 381 | FOR_EACH_TAIL (tail) | ||
| 382 | { | ||
| 383 | Lisp_Object pair = XCAR (tail); | ||
| 384 | CHECK_CONS (pair); | ||
| 385 | Lisp_Object key_symbol = XCAR (pair); | ||
| 386 | Lisp_Object value = XCDR (pair); | ||
| 387 | CHECK_SYMBOL (key_symbol); | ||
| 388 | Lisp_Object key = SYMBOL_NAME (key_symbol); | ||
| 389 | /* We can't specify the length, so the string must be | ||
| 390 | null-terminated. */ | ||
| 391 | check_string_without_embedded_nulls (key); | ||
| 392 | const char *key_str = SSDATA (key); | ||
| 393 | /* Only add element if key is not already present. */ | ||
| 394 | if (json_object_get (*json, key_str) == NULL) | ||
| 395 | { | ||
| 396 | int status | ||
| 397 | = json_object_set_new (*json, key_str, lisp_to_json (value)); | ||
| 398 | if (status == -1) | ||
| 399 | json_out_of_memory (); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | CHECK_LIST_END (tail, lisp); | ||
| 403 | clear_unwind_protect (count); | ||
| 404 | return unbind_to (count, Qnil); | ||
| 405 | } | ||
| 370 | wrong_type_argument (Qjson_value_p, lisp); | 406 | wrong_type_argument (Qjson_value_p, lisp); |
| 371 | } | 407 | } |
| 372 | 408 | ||
| 373 | /* Convert LISP to a toplevel JSON object (array or object). Signal | 409 | /* Convert LISP to a toplevel JSON object (array or object). Signal |
| 374 | an error of type `wrong-type-argument' if LISP is not a vector or | 410 | an error of type `wrong-type-argument' if LISP is not a vector, |
| 375 | hashtable. */ | 411 | hashtable, or alist. */ |
| 376 | 412 | ||
| 377 | static json_t * | 413 | static json_t * |
| 378 | lisp_to_json_toplevel (Lisp_Object lisp) | 414 | lisp_to_json_toplevel (Lisp_Object lisp) |
| @@ -413,19 +449,20 @@ lisp_to_json (Lisp_Object lisp) | |||
| 413 | return json_check (json_stringn (SSDATA (encoded), SBYTES (encoded))); | 449 | return json_check (json_stringn (SSDATA (encoded), SBYTES (encoded))); |
| 414 | } | 450 | } |
| 415 | 451 | ||
| 416 | /* LISP now must be a vector or hashtable. */ | 452 | /* LISP now must be a vector, hashtable, or alist. */ |
| 417 | return lisp_to_json_toplevel (lisp); | 453 | return lisp_to_json_toplevel (lisp); |
| 418 | } | 454 | } |
| 419 | 455 | ||
| 420 | DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, 1, NULL, | 456 | DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, 1, NULL, |
| 421 | doc: /* Return the JSON representation of OBJECT as a string. | 457 | doc: /* Return the JSON representation of OBJECT as a string. |
| 422 | OBJECT must be a vector or hashtable, and its elements can recursively | 458 | OBJECT must be a vector, hashtable, or alist, and its elements can |
| 423 | contain `:null', `:false', t, numbers, strings, or other vectors and | 459 | recursively contain `:null', `:false', t, numbers, strings, or other |
| 424 | hashtables. `:null', `:false', and t will be converted to JSON null, | 460 | vectors hashtables, and alist. `:null', `:false', and t will be |
| 425 | false, and true values, respectively. Vectors will be converted to | 461 | converted to JSON null, false, and true values, respectively. Vectors |
| 426 | JSON arrays, and hashtables to JSON objects. Hashtable keys must be | 462 | will be converted to JSON arrays, and hashtables and alists to JSON |
| 427 | strings without embedded null characters and must be unique within | 463 | objects. Hashtable keys must be strings without embedded null |
| 428 | each object. */) | 464 | characters and must be unique within each object. Alist keys must be |
| 465 | symbols; if a key is duplicate, the first instance is used. */) | ||
| 429 | (Lisp_Object object) | 466 | (Lisp_Object object) |
| 430 | { | 467 | { |
| 431 | ptrdiff_t count = SPECPDL_INDEX (); | 468 | ptrdiff_t count = SPECPDL_INDEX (); |
diff --git a/test/src/json-tests.el b/test/src/json-tests.el index 5d9f6b3840c..b23439a59fd 100644 --- a/test/src/json-tests.el +++ b/test/src/json-tests.el | |||
| @@ -50,7 +50,19 @@ | |||
| 50 | (puthash "abc" [1 2 t] table) | 50 | (puthash "abc" [1 2 t] table) |
| 51 | (puthash "def" :null table) | 51 | (puthash "def" :null table) |
| 52 | (should (equal (json-serialize table) | 52 | (should (equal (json-serialize table) |
| 53 | "{\"abc\":[1,2,true],\"def\":null}")))) | 53 | "{\"abc\":[1,2,true],\"def\":null}"))) |
| 54 | (should (equal (json-serialize '((abc . [1 2 t]) (def . :null))) | ||
| 55 | "{\"abc\":[1,2,true],\"def\":null}")) | ||
| 56 | (should (equal (json-serialize nil) "{}")) | ||
| 57 | (should (equal (json-serialize '((abc))) "{\"abc\":{}}")) | ||
| 58 | (should (equal (json-serialize '((a . 1) (b . 2) (a . 3))) | ||
| 59 | "{\"a\":1,\"b\":2}")) | ||
| 60 | (should-error (json-serialize '(abc)) :type 'wrong-type-argument) | ||
| 61 | (should-error (json-serialize '((a 1))) :type 'wrong-type-argument) | ||
| 62 | (should-error (json-serialize '((1 . 2))) :type 'wrong-type-argument) | ||
| 63 | (should-error (json-serialize '((a . 1) . b)) :type 'wrong-type-argument) | ||
| 64 | (should-error (json-serialize '#1=((a . 1) . #1#)) :type 'circular-list) | ||
| 65 | (should-error (json-serialize '(#1=(a #1#))))) | ||
| 54 | 66 | ||
| 55 | (ert-deftest json-serialize/object-with-duplicate-keys () | 67 | (ert-deftest json-serialize/object-with-duplicate-keys () |
| 56 | (skip-unless (fboundp 'json-serialize)) | 68 | (skip-unless (fboundp 'json-serialize)) |