From: "João Távora" <joaotavora@gmail.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: emacs-devel@gnu.org
Subject: Re: [PATCH] Accept plists when serializing and parsing JSON
Date: Sat, 02 Jun 2018 00:29:30 +0100 [thread overview]
Message-ID: <87d0xaozl1.fsf@gmail.com> (raw)
In-Reply-To: <83o9gug811.fsf@gnu.org> (Eli Zaretskii's message of "Fri, 01 Jun 2018 12:39:38 +0300")
[-- Attachment #1: Type: text/plain, Size: 2105 bytes --]
Eli Zaretskii <eliz@gnu.org> 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 = 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);
>
> 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 = 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 ();
}
New patch after my sig
João
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Accept-plists-when-serializing-and-parsing-JSON.patch --]
[-- Type: text/x-diff, Size: 12332 bytes --]
From 33fdc8ccf903412b47c2fa61e2ebe5f67245df73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= <joaotavora@gmail.com>
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
next prev parent reply other threads:[~2018-06-01 23:29 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-05-29 14:59 [PATCH] Accept plists when serializing and parsing JSON João Távora
2018-05-29 21:20 ` Philipp Stephani
2018-05-29 22:03 ` João Távora
2018-05-30 6:37 ` Yuri Khan
2018-05-30 8:58 ` João Távora
2018-06-02 8:04 ` Philipp Stephani
2018-06-03 0:34 ` João Távora
2018-06-03 4:05 ` Stefan Monnier
2018-06-03 13:43 ` João Távora
2018-06-02 7:45 ` Philipp Stephani
2018-06-02 7:39 ` Philipp Stephani
2018-06-01 9:39 ` Eli Zaretskii
2018-06-01 23:29 ` João Távora [this message]
2018-06-02 6:55 ` Eli Zaretskii
2018-06-02 8:24 ` Philipp Stephani
2018-06-02 9:00 ` Eli Zaretskii
2018-06-02 16:46 ` Philipp Stephani
2018-06-02 19:18 ` Eli Zaretskii
2018-06-08 14:45 ` Eli Zaretskii
2018-06-02 8:30 ` Philipp Stephani
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87d0xaozl1.fsf@gmail.com \
--to=joaotavora@gmail.com \
--cc=eliz@gnu.org \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.