From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: =?utf-8?B?Sm/Do28gVMOhdm9yYQ==?= Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] Accept plists when serializing and parsing JSON Date: Sat, 02 Jun 2018 00:29:30 +0100 Message-ID: <87d0xaozl1.fsf@gmail.com> References: <87sh6awls5.fsf@gmail.com> <83o9gug811.fsf@gnu.org> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1527895703 6668 195.159.176.226 (1 Jun 2018 23:28:23 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Fri, 1 Jun 2018 23:28:23 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.0.50 (gnu/linux) Cc: emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Jun 02 01:28:18 2018 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 1fOtSw-0001ZL-T9 for ged-emacs-devel@m.gmane.org; Sat, 02 Jun 2018 01:28:15 +0200 Original-Received: from localhost ([::1]:57813 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fOtV3-0007we-Nk for ged-emacs-devel@m.gmane.org; Fri, 01 Jun 2018 19:30:25 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:55901) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1fOtUJ-0007v0-HS for emacs-devel@gnu.org; Fri, 01 Jun 2018 19:29:41 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1fOtUG-0000bv-7j for emacs-devel@gnu.org; Fri, 01 Jun 2018 19:29:39 -0400 Original-Received: from mail-wm0-x230.google.com ([2a00:1450:400c:c09::230]:40775) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1fOtUF-0000a7-HU; Fri, 01 Jun 2018 19:29:35 -0400 Original-Received: by mail-wm0-x230.google.com with SMTP id x2-v6so5019545wmh.5; Fri, 01 Jun 2018 16:29:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=VApEs4Ko7lbMq+oeoBQ/R3xWpm6BtN56870FLGReZdU=; b=f9jphTpEYYKv1wa29yB7++tI6EZSv0xOlC+cBoYIkxrWz9XACa3JWXp2JxiSNlaOVq l5kcfEqgg3hIHgjtOJtnVxjorBhfJqYcrN1X+fs/3/P6akTZJXEV/gXSq4ozKAF/q+8i r6hKyYE6Gx5nsghwGZtmtxXCmX1uhfrS6mERTCODGGp7KyB0lhK6xfC+SaEhiPIyPP0I 8esJAsN3nCIJ2Yd8Z5B2GSLFyj/n2vkIsYUoKKUKgE5x47ILeBINQUE7xhjj+ehIpWJU N68Tab76iz2LzoNjElillal1HkKFsFxwRqr7r3YFb1zfl+ZQeF/uLHy4XCUJZr8atWaF F+mQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=VApEs4Ko7lbMq+oeoBQ/R3xWpm6BtN56870FLGReZdU=; b=Wl2AQ/Z7q5OHfcAcsFk1+qqt/Ew7EcjFnPQhdpcrgTzXQpvVFtU57Ozf66mamWTdEB HGrwTwFI2h42oOXnd4QyAZeZ6spHdac5muQ+niQCLlSCyfBFlhj/1YdOajoWnTTNOnzO gWlNdOhhZBLzpF65LWefgSiJOV362wTd0YO62/+/p+uLWGfCL9OHoRCfYcuJV0jlrsGP V+W93kyAeZobI5gSKqEWAtsDbkLejcNmFVY//6wTNbwALSieXncgDIaLp8XjQsAzIm96 DWk+jmAO+77UWhRhvnDxafBrcOHJ2Rx/+zPWcYFCs3rwka6JeAZRq6i051N7nMOss57m lMiQ== X-Gm-Message-State: ALKqPwfTXI3wDq0OLdPmI2kxNbBOYnki1NOURZv39p8wABXywAuORzWE bB3NWTAS7rFR4Yuky+UFRTARKeMf X-Google-Smtp-Source: ADUXVKLmjXvLadC6A7m0MScFOlGO7W12GmH/KH3nIsxGxtIShSItQT7oaXCAOJ015pAkNXQ6JfgfOQ== X-Received: by 2002:a1c:e0c6:: with SMTP id x189-v6mr512332wmg.0.1527895773922; Fri, 01 Jun 2018 16:29:33 -0700 (PDT) Original-Received: from lolita.yourcompany.com (188.139.62.94.rev.vodafone.pt. [94.62.139.188]) by smtp.gmail.com with ESMTPSA id f81-v6sm4440018wmh.32.2018.06.01.16.29.32 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Fri, 01 Jun 2018 16:29:33 -0700 (PDT) In-Reply-To: <83o9gug811.fsf@gnu.org> (Eli Zaretskii's message of "Fri, 01 Jun 2018 12:39:38 +0300") X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2a00:1450:400c:c09::230 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:225907 Archived-At: --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Eli Zaretskii writes: > Thanks, please see a few comments below. Thanks for reviewing. > json-serialize-use-plists is the literal name of a variable, so it > should have the @code markup, not @var. > Thanks for these doc comments, but I've gotten rid of json-serialize-use-plists, after coming to the conclusion that the map type (hashtable, alist, or plist) can be reliably auto-detected in json-serialize. As a consequence, less stuff to document.. >> + if ( EQ (Vjson_serialize_use_plists, Qt) ) { >> + key_symbol =3D XCAR (tail); >> + tail =3D XCDR(tail); >> + CHECK_CONS (tail); >> + value =3D XCAR (tail); >> + if (EQ (tail, li.tortoise)) circular_list (lisp); >> + } else { >> + Lisp_Object pair =3D XCAR (tail); > > Our style is to write > > [...] > > Also, please don't leav any whitespace between the opening parenthesis > and the following character, and similarly with closing parens. IOW, > this: OK, got it. > Here I'd use intern_1 instead, it would allow you to avoid > unnecessarily consing Lisp objects. (Yes, I realize that the same > comment applies to the existing code.) Riight, intern_1 sounds good... I know I can't just malloc() stuff as usually right? But also, I have no idea what I'm doing, I aped this from somewhere where it looked more-or-less responsibly done. json_object_foreach (json, key_str, value) { USE_SAFE_ALLOCA; char *keyword_key_str =3D SAFE_ALLOCA (1 + strlen(key_str) + 1); keyword_key_str[0]=3D':'; strcpy(&keyword_key_str[1],key_str); Lisp_Object key =3D intern_1(keyword_key_str, strlen(key_str)+1); result =3D Fcons (key, result); /* build the plist as value-key since we're going to reverse it in the end.*/ result =3D Fcons (json_to_lisp (value, object_type), result); SAFE_FREE (); } New patch after my sig Jo=C3=A3o --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-Accept-plists-when-serializing-and-parsing-JSON.patch >From 33fdc8ccf903412b47c2fa61e2ebe5f67245df73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Sat, 2 Jun 2018 00:23:38 +0100 Subject: [PATCH] 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 * test/src/json-tests.el (json-serialize/object) (json-parse-string/object): New plist tests. --- doc/lispref/text.texi | 25 ++++++----- src/json.c | 94 +++++++++++++++++++++++++++++++----------- test/src/json-tests.el | 29 ++++++++++++- 3 files changed, 113 insertions(+), 35 deletions(-) diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index da09b4ae1c..7ce79bbbea 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -5025,16 +5025,18 @@ Parsing JSON @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} is both a valid alist and a valid plist 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}. @@ -5057,12 +5059,15 @@ Parsing JSON 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 diff --git a/src/json.c b/src/json.c index b046d34f66..69c689c97c 100644 --- a/src/json.c +++ b/src/json.c @@ -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, and hashtables and alists 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,29 @@ 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; + char *keyword_key_str = SAFE_ALLOCA (1 + strlen(key_str) + 1); + keyword_key_str[0]=':'; + strcpy(&keyword_key_str[1],key_str); + Lisp_Object key = intern_1(keyword_key_str,strlen(key_str)+1); + result = Fcons (key, result); /* build the plist as + value-key since + we're going to + reverse it in the + end.*/ + result = Fcons (json_to_lisp (value, object_type), result); + SAFE_FREE (); + } + result = Fnreverse (result); + break; + } default: /* Can't get here. */ emacs_abort (); @@ -721,8 +765,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 +779,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 +959,7 @@ syms_of_json (void) DEFSYM (QCobject_type, ":object-type"); DEFSYM (Qalist, "alist"); + DEFSYM (Qplist, "plist"); defsubr (&Sjson_serialize); defsubr (&Sjson_insert); diff --git a/test/src/json-tests.el b/test/src/json-tests.el index 09067bad8c..4a01dc57cc 100644 --- a/test/src/json-tests.el +++ b/test/src/json-tests.el @@ -69,7 +69,30 @@ 'json-tests--error (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 '(: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 +112,9 @@ 'json-tests--error (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)) -- 2.17.0 --=-=-=--