From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED.blaine.gmane.org!not-for-mail From: Dmitry Gutov Newsgroups: gmane.emacs.bugs Subject: bug#32793: 27.0.50; json-parse-string doesn't have the equivalent of json.el's json-array-type Date: Fri, 12 Apr 2019 18:02:22 +0300 Message-ID: References: <83o95c4hih.fsf@gnu.org> <87b7460f-f3c8-1752-279b-ba7d25871f05@yandex.ru> <83h8b3mvbv.fsf@gnu.org> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="------------036AF5A65E9343719CAB20BA" Injection-Info: blaine.gmane.org; posting-host="blaine.gmane.org:195.159.176.226"; logging-data="220398"; mail-complaints-to="usenet@blaine.gmane.org" User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.6.1 Cc: mail@xuchunyang.me, 32793@debbugs.gnu.org To: Eli Zaretskii Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Fri Apr 12 17:03:14 2019 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([209.51.188.17]) by blaine.gmane.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1hExhx-000v9L-Ga for geb-bug-gnu-emacs@m.gmane.org; Fri, 12 Apr 2019 17:03:13 +0200 Original-Received: from localhost ([127.0.0.1]:38415 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1hExhv-0003fe-Vo for geb-bug-gnu-emacs@m.gmane.org; Fri, 12 Apr 2019 11:03:12 -0400 Original-Received: from eggs.gnu.org ([209.51.188.92]:57234) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1hExho-0003fJ-5x for bug-gnu-emacs@gnu.org; Fri, 12 Apr 2019 11:03:05 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hExhm-0005c0-Jx for bug-gnu-emacs@gnu.org; Fri, 12 Apr 2019 11:03:04 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:43073) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hExhm-0005ad-DL for bug-gnu-emacs@gnu.org; Fri, 12 Apr 2019 11:03:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1hExhm-0006Bm-5w for bug-gnu-emacs@gnu.org; Fri, 12 Apr 2019 11:03:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Dmitry Gutov Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Fri, 12 Apr 2019 15:03:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 32793 X-GNU-PR-Package: emacs Original-Received: via spool by 32793-submit@debbugs.gnu.org id=B32793.155508135623754 (code B ref 32793); Fri, 12 Apr 2019 15:03:02 +0000 Original-Received: (at 32793) by debbugs.gnu.org; 12 Apr 2019 15:02:36 +0000 Original-Received: from localhost ([127.0.0.1]:56617 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hExhL-0006B4-Rt for submit@debbugs.gnu.org; Fri, 12 Apr 2019 11:02:36 -0400 Original-Received: from mail-wm1-f42.google.com ([209.85.128.42]:40033) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hExhI-0006Am-6X for 32793@debbugs.gnu.org; Fri, 12 Apr 2019 11:02:35 -0400 Original-Received: by mail-wm1-f42.google.com with SMTP id z24so11501349wmi.5 for <32793@debbugs.gnu.org>; Fri, 12 Apr 2019 08:02:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:subject:to:cc:references:from:message-id:date:user-agent :mime-version:in-reply-to:content-language; bh=2S+uLBkeSxrE5Htd5QJI6nY+MjfMLaDZPgE3Gj97Nc0=; b=aQlMpKjUKm7PutnxhP9JwWIdzjeyovxqpWrSUyGHGdMppDuF3HJSrRc/75G0L2dutR +8eKHHSvdyP48ONV+Mesei7UdKqSN0/hH8gI0O50JJcOe/DVH3elK2cKvDUHd6MfD+BF WTc/cW6nr5R1dF8qY52Lo4ov1CWslgs4G041WZrmwMn/lq9RRJXBsfhy+YSEmv7+zUwj 3Ezn6Mj3e+Tjgicqgsp68Gic2BOhvmjKjrN+sqNCS9oD1766Mx0JdIjRgbBhMSaglZrA bv5sBj0FtJ7RtQbUA32GwQkGi+uknqj5BdXKROgxdb04RzYo2STKPAoYlQCTeVHQhIXN /Saw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:subject:to:cc:references:from:message-id :date:user-agent:mime-version:in-reply-to:content-language; bh=2S+uLBkeSxrE5Htd5QJI6nY+MjfMLaDZPgE3Gj97Nc0=; b=ao8x0S7B7MPQXwoskFKXNt8R2u1CrI14U6NkYLj5q98YXiGwffUnAtnxw1Fobhttro BYYb/BibLyEM5Et3M+TdAoIMM+l/Zzo6ogGHAQtDZzZQaar4M07YGWI8zStq8Bh44Dn2 oetZ0iDqz3GqgN7d/IRyXopv3RSRsQlp83aEYxhMxKYGb1ykHVFKTudaBg+HL3F9kw/o NGtrwsDYB8e2vaWeg351zGMVCz2EKyMDdjBbITAzAKsUxSJ0S1RLg0Q9XHvhNtPEA63g LYhso+9hbSrPrvGGCsg1YKPcz5i9c4aYrLcEbSp1ro8fd746ok8hddk7z9AEzKteoeBq TEmg== X-Gm-Message-State: APjAAAVHqb8EJplfKoJZCGxfxqUG9SYYewWboljZSfT57zrHE7LHfLyZ epsf7fi2m2ibIF/2pywZTzD3cKAJ X-Google-Smtp-Source: APXvYqzYDFtUxFjGDKqKG2wn4RRDOhhoVfDkKb1SHo7bVRoxVF6LiPQCqsiIC1RHFSslO0BK9DUXuw== X-Received: by 2002:a1c:d7:: with SMTP id 206mr11010089wma.69.1555081344794; Fri, 12 Apr 2019 08:02:24 -0700 (PDT) Original-Received: from [192.168.0.195] ([109.110.245.170]) by smtp.googlemail.com with ESMTPSA id y132sm10620097wmg.38.2019.04.12.08.02.22 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 12 Apr 2019 08:02:23 -0700 (PDT) In-Reply-To: <83h8b3mvbv.fsf@gnu.org> Content-Language: en-US X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.51.188.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.org gmane.emacs.bugs:157555 Archived-At: This is a multi-part message in MIME format. --------------036AF5A65E9343719CAB20BA Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit On 12.04.2019 12:22, Eli Zaretskii wrote: > Can't say I like this conversion of Lisp symbols into C enumerations. > I'd rather we used symbols (Qarray, Qlist, etc.), but I can understand > why you did this as json.c already did for the other keyword values. I vaguely suspect it might help with performance. *shrug* Or not. >> @@ -521,7 +527,7 @@ static void >> json_parse_args (ptrdiff_t nargs, >> Lisp_Object *args, >> struct json_configuration *conf, >> - bool configure_object_type) >> + bool configure_types) > > If we are renaming this argument, let's do a better job: I think its > name should have been parse_object_types. OK. > Come to think of this: why do we need this boolean at all? The > callers which don't want :object-type parsed will ignore the result > anyway, so it sounds like something we could just toss. I'd rather we didn't accept argument we cannot handle, to avoid false expectations. For example with this patch we can parse a JSON array into a Lisp list. But there's no way to serialize a list back into a JSON array, yet. I looked at implementing it, just for completeness (it's not necessary for my use case, for now). But there's an ambiguity between lists and alists which is not trivial to solve (i.e. when object-type is alist and array-type is list, how do we determinine, quickly and reliably, that a given list is actually an alist?). So maybe leave it for the future. >> + case json_array_list: >> + { >> + result = Qnil; >> + for (ptrdiff_t i = 0; i < size; ++i) >> + result = Fcons (json_to_lisp (json_array_get (json, i), conf), >> + result); >> + result = Fnreverse (result); > > If you cons the list back to front, you can avoid the Fnreverse call, > which will make this faster. Done. No real performance impact that I can see, but it didn't hurt either. > Also, please insert a call to rarely_quit into the loop, as JSON > vectors could be quite large, AFAIU. Also done. In the json_array_array case as well, where it was missing, it seems. > Finally, this needs documentation update: the doc strings of > json-parse-string and json-parse-buffer, NEWS, and the ELisp manual. json-parse-buffer and NEWS don't need updating, I think. Otherwise, done, see the new patch. --------------036AF5A65E9343719CAB20BA Content-Type: text/x-patch; name="json-array-type.diff" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="json-array-type.diff" diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index 1ef836b8f9..b46ee64786 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -5167,6 +5167,11 @@ Parsing JSON keys; @code{alist} to use alists with symbols as keys; or @code{plist} to use plists with keyword symbols as keys. +@item :array-type +The value decides which Lisp object to use for representing a JSON +array. It can be either @code{array}, the default, to use Lisp +arrays; or @code{list} to use lists. + @item :null-object The value decides which Lisp object to use to represent the JSON keyword @code{null}. It defaults to the symbol @code{:null}. diff --git a/src/json.c b/src/json.c index 5e1439f881..1f45fe527f 100644 --- a/src/json.c +++ b/src/json.c @@ -337,8 +337,14 @@ enum json_object_type { json_object_plist }; +enum json_array_type { + json_array_array, + json_array_list +}; + struct json_configuration { enum json_object_type object_type; + enum json_array_type array_type; Lisp_Object null_object; Lisp_Object false_object; }; @@ -521,7 +527,7 @@ static void json_parse_args (ptrdiff_t nargs, Lisp_Object *args, struct json_configuration *conf, - bool configure_object_type) + bool parse_object_types) { if ((nargs % 2) != 0) wrong_type_argument (Qplistp, Flist (nargs, args)); @@ -531,7 +537,7 @@ json_parse_args (ptrdiff_t nargs, for (ptrdiff_t i = nargs; i > 0; i -= 2) { Lisp_Object key = args[i - 2]; Lisp_Object value = args[i - 1]; - if (configure_object_type && EQ (key, QCobject_type)) + if (parse_object_types && EQ (key, QCobject_type)) { if (EQ (value, Qhash_table)) conf->object_type = json_object_hashtable; @@ -542,12 +548,22 @@ json_parse_args (ptrdiff_t nargs, else wrong_choice (list3 (Qhash_table, Qalist, Qplist), value); } + else if (parse_object_types && EQ (key, QCarray_type)) + { + if (EQ (value, Qarray)) + conf->array_type = json_array_array; + else if (EQ (value, Qlist)) + conf->array_type = json_array_list; + else + wrong_choice (list2 (Qarray, Qlist), value); + } else if (EQ (key, QCnull_object)) conf->null_object = value; else if (EQ (key, QCfalse_object)) conf->false_object = value; - else if (configure_object_type) - wrong_choice (list3 (QCobject_type, + else if (parse_object_types) + wrong_choice (list4 (QCobject_type, + QCarray_type, QCnull_object, QCfalse_object), value); @@ -604,7 +620,8 @@ usage: (json-serialize OBJECT &rest ARGS) */) } #endif - struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; + struct json_configuration conf = + {json_object_hashtable, json_array_array, QCnull, QCfalse}; json_parse_args (nargs - 1, args + 1, &conf, false); json_t *json = lisp_to_json_toplevel (args[0], &conf); @@ -701,7 +718,8 @@ usage: (json-insert OBJECT &rest ARGS) */) } #endif - struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; + struct json_configuration conf = + {json_object_hashtable, json_array_array, QCnull, QCfalse}; json_parse_args (nargs - 1, args + 1, &conf, false); json_t *json = lisp_to_json (args[0], &conf); @@ -817,10 +835,35 @@ json_to_lisp (json_t *json, struct json_configuration *conf) size_t size = json_array_size (json); if (PTRDIFF_MAX < size) overflow_error (); - Lisp_Object result = make_vector (size, Qunbound); - for (ptrdiff_t i = 0; i < size; ++i) - ASET (result, i, - json_to_lisp (json_array_get (json, i), conf)); + Lisp_Object result; + switch (conf->array_type) + { + case json_array_array: + { + result = make_vector (size, Qunbound); + for (ptrdiff_t i = 0; i < size; ++i) + { + rarely_quit (i); + ASET (result, i, + json_to_lisp (json_array_get (json, i), conf)); + } + break; + } + case json_array_list: + { + result = Qnil; + for (ptrdiff_t i = size - 1; i >= 0; --i) + { + rarely_quit (i); + result = Fcons (json_to_lisp (json_array_get (json, i), conf), + result); + } + break; + } + default: + /* Can't get here. */ + emacs_abort (); + } --lisp_eval_depth; return result; } @@ -918,6 +961,9 @@ a list of keyword/argument pairs: The keyword argument `:object-type' specifies which Lisp type is used to represent objects; it can be `hash-table', `alist' or `plist'. +The keyword argument `:array-type' specifies which Lisp type is used +to represent arrays; it can be `array' or `list'. + The keyword argument `:null-object' specifies which object to use to represent a JSON null value. It defaults to `:null'. @@ -946,7 +992,8 @@ usage: (json-parse-string STRING &rest ARGS) */) Lisp_Object string = args[0]; Lisp_Object encoded = json_encode (string); check_string_without_embedded_nuls (encoded); - struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; + struct json_configuration conf = + {json_object_hashtable, json_array_array, QCnull, QCfalse}; json_parse_args (nargs - 1, args + 1, &conf, true); json_error_t error; @@ -1016,7 +1063,8 @@ usage: (json-parse-buffer &rest args) */) } #endif - struct json_configuration conf = {json_object_hashtable, QCnull, QCfalse}; + struct json_configuration conf = + {json_object_hashtable, json_array_array, QCnull, QCfalse}; json_parse_args (nargs, args, &conf, true); ptrdiff_t point = PT_BYTE; @@ -1095,10 +1143,12 @@ syms_of_json (void) Fput (Qjson_parse_string, Qside_effect_free, Qt); DEFSYM (QCobject_type, ":object-type"); + DEFSYM (QCarray_type, ":array-type"); DEFSYM (QCnull_object, ":null-object"); DEFSYM (QCfalse_object, ":false-object"); DEFSYM (Qalist, "alist"); DEFSYM (Qplist, "plist"); + DEFSYM (Qarray, "array"); defsubr (&Sjson_serialize); defsubr (&Sjson_insert); diff --git a/test/src/json-tests.el b/test/src/json-tests.el index 04f91f4abb..542eec11bf 100644 --- a/test/src/json-tests.el +++ b/test/src/json-tests.el @@ -117,6 +117,14 @@ 'json-tests--error (should (equal (json-parse-string input :object-type 'plist) '(:abc [9 :false] :def :null))))) +(ert-deftest json-parse-string/array () + (skip-unless (fboundp 'json-parse-string)) + (let ((input "[\"a\", 1, [\"b\", 2]]")) + (should (equal (json-parse-string input) + ["a" 1 ["b" 2]])) + (should (equal (json-parse-string input :array-type 'list) + '("a" 1 ("b" 2)))))) + (ert-deftest json-parse-string/string () (skip-unless (fboundp 'json-parse-string)) (should-error (json-parse-string "[\"formfeed\f\"]") :type 'json-parse-error) --------------036AF5A65E9343719CAB20BA--