From mboxrd@z Thu Jan 1 00:00:00 1970
Path: news.gmane.org!.POSTED!not-for-mail
From: Philipp Stephani
Newsgroups: gmane.emacs.devel
Subject: [PATCH] Accept alists when serializing JSON
Date: Mon, 18 Dec 2017 21:26:33 +0100
Message-ID: <20171218202633.94376-1-phst@google.com>
References:
NNTP-Posting-Host: blaine.gmane.org
X-Trace: blaine.gmane.org 1513628735 15062 195.159.176.226 (18 Dec 2017 20:25:35 GMT)
X-Complaints-To: usenet@blaine.gmane.org
NNTP-Posting-Date: Mon, 18 Dec 2017 20:25:35 +0000 (UTC)
Cc: Philipp Stephani
To: Vibhav Pant ,
emacs-devel@gnu.org
Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Mon Dec 18 21:25:31 2017
Return-path:
Envelope-to: ged-emacs-devel@m.gmane.org
Original-Received: from lists.gnu.org ([208.118.235.17])
by blaine.gmane.org with esmtp (Exim 4.84_2)
(envelope-from )
id 1eR1yb-0003KV-U2
for ged-emacs-devel@m.gmane.org; Mon, 18 Dec 2017 21:25:30 +0100
Original-Received: from localhost ([::1]:39005 helo=lists.gnu.org)
by lists.gnu.org with esmtp (Exim 4.71)
(envelope-from )
id 1eR20Y-0007eO-Jn
for ged-emacs-devel@m.gmane.org; Mon, 18 Dec 2017 15:27:30 -0500
Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:40008)
by lists.gnu.org with esmtp (Exim 4.71)
(envelope-from ) id 1eR1zw-0007de-Vf
for emacs-devel@gnu.org; Mon, 18 Dec 2017 15:26:54 -0500
Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71)
(envelope-from ) id 1eR1zt-0006VX-7F
for emacs-devel@gnu.org; Mon, 18 Dec 2017 15:26:52 -0500
Original-Received: from mail-wr0-x235.google.com ([2a00:1450:400c:c0c::235]:34956)
by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16)
(Exim 4.71) (envelope-from )
id 1eR1zs-0006UI-U4
for emacs-devel@gnu.org; Mon, 18 Dec 2017 15:26:49 -0500
Original-Received: by mail-wr0-x235.google.com with SMTP id l19so3235352wrc.2
for ; Mon, 18 Dec 2017 12:26:48 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025;
h=from:to:cc:subject:date:message-id:in-reply-to:references;
bh=zMEIoXxHkBd2E1oVhEVv6WwRg6WK8JBPZG24KkJPrZQ=;
b=Tl2b1bq5sKLNQvRrJiFXguOuvJTM7yGhxB3V4FgMLwm6uSZYqUoeLlPCAe9r9/RSdQ
shA4b0/tfoShKuOQXZfXBIOV+uYT9lidMmmyO2grGZtiEUqKgC97TYfpwC9YEpckCC02
X99so7MN9w2VeETGzFtrSoRWheq8C1VByWWC4v4r4J5AnLAePEiREfFPo3FEBlb1iHgb
DPohcG4F8B38zXUXNc3do48hh85nTFpBLS6Y0WQ/40QEPoqUo9BHmWCGpXPjBrHhEb3G
HBvxD2KMngPbvwVZ6SXOA2xbC0h0JOdUjVyW3dwNvBXodM2JhRrRB19NU0ZqiovYb7wX
qtqA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=1e100.net; s=20161025;
h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to
:references;
bh=zMEIoXxHkBd2E1oVhEVv6WwRg6WK8JBPZG24KkJPrZQ=;
b=tdQCdFl/NORlTRG4gJFlJEvh/KDBSOND9cgD8mEnljYlI4IPfT7/zThfEiSAuoNtEh
ztCI370aySafH4lCnGnV8fJe4rxsTrZzw2YFARY5pwGJUGbTQwJunSp06oio8oGKbSmB
YYQriVfezR3BIGe/qPBaGyQedlfKH1tKBJMSwn8nV1bea++QYXXBU1jmJqTSfwsHLFES
L/W8fn4d1ydO8MV80XMM6RpXIWGQMu67QylL2AQ61wlERTTVoz6Bl4z/pzH6n5aP4PdL
JF1VqlINgd6aWMwjcTQVDyB9vHsXK60oVAFcm4L85oLdcAdqRM2YmQjJX10eAoEPzWkq
jvxg==
X-Gm-Message-State: AKGB3mLIWSltnl9ZpU2aknVMzzyiZVp8TjoU1NZ8SytiedErcjoDOLj+
ZwZz03NaSJIQs3mrSQepCfE=
X-Google-Smtp-Source: ACJfBothY52mFuoplcRztuLghtN2A+zdGjIF2UkFWwrzmyyjpsCnw6/y2Bnzitwjfwtlm+34wYvnfQ==
X-Received: by 10.223.161.14 with SMTP id o14mr1406948wro.111.1513628807698;
Mon, 18 Dec 2017 12:26:47 -0800 (PST)
Original-Received: from p.fritz.box (p5B13F7C7.dip0.t-ipconnect.de. [91.19.247.199])
by smtp.gmail.com with ESMTPSA id g78sm168290wmc.30.2017.12.18.12.26.46
(version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128);
Mon, 18 Dec 2017 12:26:47 -0800 (PST)
X-Google-Original-From: Philipp Stephani
X-Mailer: git-send-email 2.15.1
In-Reply-To:
X-detected-operating-system: by eggs.gnu.org: Genre and OS details not
recognized.
X-Received-From: 2a00:1450:400c:c0c::235
X-BeenThere: emacs-devel@gnu.org
X-Mailman-Version: 2.1.21
Precedence: list
List-Id: "Emacs development discussions."
List-Unsubscribe: ,
List-Archive:
List-Post:
List-Help:
List-Subscribe: ,
Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org
Original-Sender: "Emacs-devel"
Xref: news.gmane.org gmane.emacs.devel:221226
Archived-At:
* 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.
---
doc/lispref/text.texi | 10 +++++----
src/json.c | 57 +++++++++++++++++++++++++++++++++++++++++---------
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 7517dbcfb6..fbc4352fec 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -4965,14 +4965,16 @@ Parsing JSON
@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}.
diff --git a/src/json.c b/src/json.c
index 6b8387ff2f..747b402f96 100644
--- a/src/json.c
+++ b/src/json.c
@@ -335,12 +335,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_data = SSDATA (key);
+ /* Only add element if key is not already present. */
+ if (json_object_get (*json, key_data) == NULL)
+ {
+ int status
+ = json_object_set_new (*json, key_data, 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)
@@ -380,19 +416,20 @@ lisp_to_json (Lisp_Object lisp)
return json_check (json_stringn (SSDATA (encoded), size));
}
- /* 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 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. Alist keys must be symbols; if a key is
+duplicate, the first instance is used. */)
(Lisp_Object object)
{
ptrdiff_t count = SPECPDL_INDEX ();
diff --git a/test/src/json-tests.el b/test/src/json-tests.el
index da51aac8c8..e857753cdc 100644
--- a/test/src/json-tests.el
+++ b/test/src/json-tests.el
@@ -48,7 +48,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-parse-string/object ()
(skip-unless (fboundp 'json-parse-string))
--
2.15.1