From: Vibhav Pant <vibhavp@gmail.com>
To: emacs-devel@gnu.org
Cc: Vibhav Pant <vibhavp@gmail.com>
Subject: [PATCH] Allow native JSON functions to specify values for false/null.
Date: Sun, 31 Dec 2017 01:37:52 +0530 [thread overview]
Message-ID: <20171230200752.19371-1-vibhavp@gmail.com> (raw)
* src/fns.c (get_key_arg): Make function public/non-static.
* src/lisp.h: Add extern declaration for get_key_arg.
* src/json.c: Add new struct `json_values'.
* src/json.c (lisp_to_json, lisp_to_json_toplevel_1,
lisp_to_json_toplevel, lisp_to_json): Take new parameter,
json_values.
* src/json.c (json-serialize, json-insert, json-parse-string,
json-parse-buffer): Add two new keyword arguments, :json-false and
:json-null. Encoding and parsing JSON will use their values for
representing false and null respectively.
* src/json.c (json_parse_object_type): Use get_key_arg for getting the
value of :object-type, taking (char *) as a new argument.
* src/json.c (syms_of_json): Define symbols :json-false and
:json-null.
* test/src/json-tests.el (json-serialize/json-false,
json-serialize/json-null, json-parse-string/json-false,
json-parse-string/json-null): Add tests for custom falsy values
using :json-false and :json-null.
---
src/fns.c | 2 +-
src/json.c | 209 ++++++++++++++++++++++++++++++++++---------------
src/lisp.h | 1 +
test/src/json-tests.el | 28 +++++++
4 files changed, 175 insertions(+), 65 deletions(-)
diff --git a/src/fns.c b/src/fns.c
index 9db9bea9f7..6193319228 100644
--- a/src/fns.c
+++ b/src/fns.c
@@ -3558,7 +3558,7 @@ next_almost_prime (EMACS_INT n)
0. This function is used to extract a keyword/argument pair from
a DEFUN parameter list. */
-static ptrdiff_t
+ptrdiff_t
get_key_arg (Lisp_Object key, ptrdiff_t nargs, Lisp_Object *args, char *used)
{
ptrdiff_t i;
diff --git a/src/json.c b/src/json.c
index 88db86ad2e..f620d09588 100644
--- a/src/json.c
+++ b/src/json.c
@@ -155,6 +155,12 @@ init_json_functions (void)
#endif /* WINDOWSNT */
+struct json_values
+{
+ Lisp_Object json_false;
+ Lisp_Object json_null;
+};
+
/* We install a custom allocator so that we can avoid objects larger
than PTRDIFF_MAX. Such objects wouldn't play well with the rest of
Emacs's codebase, which generally uses ptrdiff_t for sizes and
@@ -316,14 +322,14 @@ json_check (json_t *object)
return object;
}
-static json_t *lisp_to_json (Lisp_Object);
+static json_t *lisp_to_json (Lisp_Object, struct json_values *);
/* Convert a Lisp object to a toplevel JSON object (array or object).
This returns Lisp_Object so we can use unbind_to. The return value
is always nil. */
static _GL_ARG_NONNULL ((2)) Lisp_Object
-lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
+lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json, struct json_values *values)
{
if (VECTORP (lisp))
{
@@ -334,7 +340,7 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
for (ptrdiff_t i = 0; i < size; ++i)
{
int status
- = json_array_append_new (*json, lisp_to_json (AREF (lisp, i)));
+ = json_array_append_new (*json, lisp_to_json (AREF (lisp, i), values));
if (status == -1)
json_out_of_memory ();
}
@@ -361,7 +367,7 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
if (json_object_get (*json, key_str) != NULL)
wrong_type_argument (Qjson_value_p, lisp);
int status = json_object_set_new (*json, key_str,
- lisp_to_json (HASH_VALUE (h, i)));
+ lisp_to_json (HASH_VALUE (h, i), values));
if (status == -1)
/* FIXME: A failure here might also indicate that the
key is not a valid Unicode string. */
@@ -397,7 +403,7 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
if (json_object_get (*json, key_str) == NULL)
{
int status
- = json_object_set_new (*json, key_str, lisp_to_json (value));
+ = json_object_set_new (*json, key_str, lisp_to_json (value, values));
if (status == -1)
json_out_of_memory ();
}
@@ -414,12 +420,12 @@ lisp_to_json_toplevel_1 (Lisp_Object lisp, json_t **json)
hashtable, or alist. */
static json_t *
-lisp_to_json_toplevel (Lisp_Object lisp)
+lisp_to_json_toplevel (Lisp_Object lisp, struct json_values *values)
{
if (++lisp_eval_depth > max_lisp_eval_depth)
xsignal0 (Qjson_object_too_deep);
json_t *json;
- lisp_to_json_toplevel_1 (lisp, &json);
+ lisp_to_json_toplevel_1 (lisp, &json, values);
--lisp_eval_depth;
return json;
}
@@ -429,11 +435,11 @@ lisp_to_json_toplevel (Lisp_Object lisp)
JSON object. */
static json_t *
-lisp_to_json (Lisp_Object lisp)
+lisp_to_json (Lisp_Object lisp, struct json_values *values)
{
- if (EQ (lisp, QCnull))
+ if (EQ (lisp, values->json_null))
return json_check (json_null ());
- else if (EQ (lisp, QCfalse))
+ else if (EQ (lisp, values->json_false))
return json_check (json_false ());
else if (EQ (lisp, Qt))
return json_check (json_true ());
@@ -453,10 +459,11 @@ lisp_to_json (Lisp_Object lisp)
}
/* LISP now must be a vector, hashtable, or alist. */
- return lisp_to_json_toplevel (lisp);
+ return lisp_to_json_toplevel (lisp, values);
}
-DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, 1, NULL,
+DEFUN ("json-serialize", Fjson_serialize, Sjson_serialize, 1, MANY,
+ 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
@@ -465,10 +472,19 @@ 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. */)
- (Lisp_Object object)
+symbols; if a key is duplicate, the first instance is used. The following
+keyword arguments are defined:
+
+:json-false FALSE -- Value to use while reading JSON `false', instead of
+`:false'.
+
+:json-null NULL -- Value to use while reading JSON `null', instead of
+`:null'.
+
+usage: (json-serialize OBJECT &rest KEYWORD-ARGS) */)
+ (ptrdiff_t nargs, Lisp_Object *args)
{
- ptrdiff_t count = SPECPDL_INDEX ();
+ ptrdiff_t i;
#ifdef WINDOWSNT
if (!json_initialized)
@@ -485,7 +501,23 @@ symbols; if a key is duplicate, the first instance is used. */)
}
#endif
- json_t *json = lisp_to_json_toplevel (object);
+ USE_SAFE_ALLOCA;
+ Lisp_Object object = args[0];
+ struct json_values values;
+ char *used = SAFE_ALLOCA ((nargs - 1) * sizeof *used);
+ memset (used, 0, (nargs - 1) * sizeof *used);
+
+ i = get_key_arg (QCjson_false, nargs - 1, args + 1, used);
+ values.json_false = i ? args[i + 1] : QCfalse;
+ i = get_key_arg (QCjson_null, nargs - 1, args + 1, used);
+ values.json_null = i ? args[i + 1] : QCnull;
+ for (i = 0; i < nargs - 1; i++)
+ if (!used[i])
+ signal_error ("Invalid argument list", args[i + 1]);
+ SAFE_FREE ();
+
+ ptrdiff_t count = SPECPDL_INDEX ();
+ json_t *json = lisp_to_json_toplevel (object, &values);
record_unwind_protect_ptr (json_release_object, json);
/* If desired, we might want to add the following flags:
@@ -541,14 +573,15 @@ json_insert_callback (const char *buffer, size_t size, void *data)
return NILP (d->error) ? 0 : -1;
}
-DEFUN ("json-insert", Fjson_insert, Sjson_insert, 1, 1, NULL,
+DEFUN ("json-insert", Fjson_insert, Sjson_insert, 1, MANY, NULL,
doc: /* Insert the JSON representation of OBJECT before point.
This is the same as (insert (json-serialize OBJECT)), but potentially
faster. See the function `json-serialize' for allowed values of
-OBJECT. */)
- (Lisp_Object object)
+OBJECT and supported keyword arguments.
+usage: (json-insert OBJECT &rest KEYWORD-ARGS)*/)
+ (ptrdiff_t nargs, Lisp_Object *args)
{
- ptrdiff_t count = SPECPDL_INDEX ();
+ ptrdiff_t i;
#ifdef WINDOWSNT
if (!json_initialized)
@@ -565,7 +598,23 @@ OBJECT. */)
}
#endif
- json_t *json = lisp_to_json (object);
+ USE_SAFE_ALLOCA;
+ Lisp_Object object = args[0];
+ struct json_values values;
+ char *used = SAFE_ALLOCA ((nargs - 1) * sizeof *used);
+ memset (used, 0, (nargs - 1) * sizeof *used);
+
+ i = get_key_arg (QCjson_false, nargs - 1, args + 1, used);
+ values.json_false = i ? args[i + 1] : QCfalse;
+ i = get_key_arg (QCjson_null, nargs - 1, args + 1, used);
+ values.json_null = i ? args[i + 1] : QCnull;
+ for (i = 0; i < nargs - 1; i++)
+ if (!used[i])
+ signal_error ("Invalid argument list", args[i + 1]);
+ SAFE_FREE ();
+
+ ptrdiff_t count = SPECPDL_INDEX ();
+ json_t *json = lisp_to_json (object, &values);
record_unwind_protect_ptr (json_release_object, json);
struct json_insert_data data;
@@ -581,7 +630,7 @@ OBJECT. */)
json_out_of_memory ();
}
- return unbind_to (count, Qnil);
+ return unbind_to(count, Qnil);
}
enum json_object_type {
@@ -592,14 +641,14 @@ enum json_object_type {
/* Convert a JSON object to a Lisp object. */
static _GL_ARG_NONNULL ((1)) Lisp_Object
-json_to_lisp (json_t *json, enum json_object_type object_type)
+json_to_lisp (json_t *json, enum json_object_type object_type, struct json_values *values)
{
switch (json_typeof (json))
{
case JSON_NULL:
- return QCnull;
+ return values->json_null;
case JSON_FALSE:
- return QCfalse;
+ return values->json_false;
case JSON_TRUE:
return Qt;
case JSON_INTEGER:
@@ -626,7 +675,7 @@ json_to_lisp (json_t *json, enum json_object_type object_type)
Lisp_Object result = Fmake_vector (make_natnum (size), Qunbound);
for (ptrdiff_t i = 0; i < size; ++i)
ASET (result, i,
- json_to_lisp (json_array_get (json, i), object_type));
+ json_to_lisp (json_array_get (json, i), object_type, values));
--lisp_eval_depth;
return result;
}
@@ -655,7 +704,7 @@ json_to_lisp (json_t *json, enum json_object_type object_type)
/* Keys in JSON objects are unique, so the key can't
be present yet. */
eassert (i < 0);
- hash_put (h, key, json_to_lisp (value, object_type), hash);
+ hash_put (h, key, json_to_lisp (value, object_type, values), hash);
}
break;
}
@@ -668,7 +717,7 @@ json_to_lisp (json_t *json, enum json_object_type object_type)
{
Lisp_Object key = Fintern (json_build_string (key_str), Qnil);
result
- = Fcons (Fcons (key, json_to_lisp (value, object_type)),
+ = Fcons (Fcons (key, json_to_lisp (value, object_type, values)),
result);
}
result = Fnreverse (result);
@@ -687,28 +736,23 @@ json_to_lisp (json_t *json, enum json_object_type object_type)
}
static enum json_object_type
-json_parse_object_type (ptrdiff_t nargs, Lisp_Object *args)
+json_parse_object_type (ptrdiff_t nargs, Lisp_Object *args, char *used)
{
- switch (nargs)
- {
- case 0:
- return json_object_hashtable;
- case 2:
- {
- Lisp_Object key = args[0];
- Lisp_Object value = args[1];
- if (!EQ (key, QCobject_type))
- wrong_choice (list1 (QCobject_type), key);
- if (EQ (value, Qhash_table))
- return json_object_hashtable;
- else if (EQ (value, Qalist))
- return json_object_alist;
- else
- wrong_choice (list2 (Qhash_table, Qalist), value);
- }
- default:
- wrong_type_argument (Qplistp, Flist (nargs, args));
- }
+ ptrdiff_t i;
+ enum json_object_type type;
+
+ i = get_key_arg (QCobject_type, nargs, args, used);
+
+ if (!i)
+ type = json_object_hashtable;
+ else if (EQ (args[i], Qhash_table))
+ type = json_object_hashtable;
+ else if (EQ (args[i], Qalist))
+ type = json_object_alist;
+ else
+ wrong_choice (list2 (Qhash_table, Qalist), args[i]);
+
+ return type;
}
DEFUN ("json-parse-string", Fjson_parse_string, Sjson_parse_string, 1, MANY,
@@ -720,13 +764,21 @@ 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: (string &key (OBJECT-TYPE \\='hash-table)) */)
+The following keyword arguments are defined:
+
+`:object-type' TYPE -- Lisp type to be used to represent objects, can
+be `hash-table' or `alist'.
+
+:json-false FALSE -- Value to use while reading JSON `false', instead of
+`:false'.
+
+:json-null NULL -- Value to use while reading JSON `null', instead of
+`:null'.
+
+usage: (json-parse-string STRING &rest KEYWORD-ARGS) */)
(ptrdiff_t nargs, Lisp_Object *args)
{
- ptrdiff_t count = SPECPDL_INDEX ();
-
+ ptrdiff_t i;
#ifdef WINDOWSNT
if (!json_initialized)
{
@@ -742,11 +794,26 @@ usage: (string &key (OBJECT-TYPE \\='hash-table)) */)
}
#endif
+ USE_SAFE_ALLOCA;
Lisp_Object string = args[0];
+ struct json_values values;
+ char *used = SAFE_ALLOCA ((nargs - 1) * sizeof *used);
+ memset (used, 0, (nargs - 1) * sizeof *used);
+ i = get_key_arg (QCjson_false, nargs - 1, args + 1, used);
+ values.json_false = i ? args[i + 1] : QCfalse;
+ i = get_key_arg (QCjson_null, nargs - 1, args + 1, used);
+ values.json_null = i ? args[i + 1] : QCnull;
+ enum json_object_type object_type =
+ json_parse_object_type (nargs - 1, args + 1, used);
+ for (i = 0; i < nargs - 1; i++)
+ if (!used[i])
+ signal_error ("Invalid argument list", args[i + 1]);
+ SAFE_FREE ();
+
+ ptrdiff_t count = SPECPDL_INDEX ();
+
Lisp_Object encoded = json_encode (string);
check_string_without_embedded_nulls (encoded);
- enum json_object_type object_type
- = json_parse_object_type (nargs - 1, args + 1);
json_error_t error;
json_t *object = json_loads (SSDATA (encoded), 0, &error);
@@ -757,7 +824,7 @@ usage: (string &key (OBJECT-TYPE \\='hash-table)) */)
if (object != NULL)
record_unwind_protect_ptr (json_release_object, object);
- return unbind_to (count, json_to_lisp (object, object_type));
+ return unbind_to (count, json_to_lisp (object, object_type, &values));
}
struct json_read_buffer_data
@@ -795,11 +862,10 @@ DEFUN ("json-parse-buffer", Fjson_parse_buffer, Sjson_parse_buffer,
This is similar to `json-parse-string', which see. Move point after
the end of the object if parsing was successful. On error, point is
not moved.
-usage: (&key (OBJECT-TYPE \\='hash-table)) */)
+usage: (json-parse-buffer &rest KEYWORD-ARGS) */)
(ptrdiff_t nargs, Lisp_Object *args)
{
- ptrdiff_t count = SPECPDL_INDEX ();
-
+ ptrdiff_t i;
#ifdef WINDOWSNT
if (!json_initialized)
{
@@ -815,9 +881,22 @@ usage: (&key (OBJECT-TYPE \\='hash-table)) */)
}
#endif
- enum json_object_type object_type = json_parse_object_type (nargs, args);
-
- ptrdiff_t point = PT_BYTE;
+ USE_SAFE_ALLOCA;
+ struct json_values values;
+ char *used = SAFE_ALLOCA (nargs * sizeof *used);
+ memset (used, 0, nargs * sizeof *used);
+
+ i = get_key_arg (QCjson_false, nargs, args, used);
+ values.json_false = i ? args[i] : QCfalse;
+ i = get_key_arg (QCjson_null, nargs, args, used);
+ values.json_null = i ? args[i] : QCnull;
+ enum json_object_type object_type = json_parse_object_type (nargs, args, used);
+ for (i = 0; i < nargs; i++)
+ if (!used[i])
+ signal_error ("Invalid argument list", args[i + 1]);
+ SAFE_FREE ();
+
+ ptrdiff_t point = PT_BYTE, count = SPECPDL_INDEX ();
struct json_read_buffer_data data = {.point = point};
json_error_t error;
json_t *object = json_load_callback (json_read_buffer_callback, &data,
@@ -830,7 +909,7 @@ usage: (&key (OBJECT-TYPE \\='hash-table)) */)
record_unwind_protect_ptr (json_release_object, object);
/* Convert and then move point only if everything succeeded. */
- Lisp_Object lisp = json_to_lisp (object, object_type);
+ Lisp_Object lisp = json_to_lisp (object, object_type, &values);
/* Adjust point by how much we just read. */
point += error.position;
@@ -894,6 +973,8 @@ syms_of_json (void)
Fput (Qjson_parse_string, Qside_effect_free, Qt);
DEFSYM (QCobject_type, ":object-type");
+ DEFSYM (QCjson_false, ":json-false");
+ DEFSYM (QCjson_null, ":json-null");
DEFSYM (Qalist, "alist");
defsubr (&Sjson_serialize);
diff --git a/src/lisp.h b/src/lisp.h
index eb31ba209a..9c54913972 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -3494,6 +3494,7 @@ extern void syms_of_syntax (void);
/* Defined in fns.c. */
enum { NEXT_ALMOST_PRIME_LIMIT = 11 };
extern EMACS_INT next_almost_prime (EMACS_INT) ATTRIBUTE_CONST;
+extern ptrdiff_t get_key_arg (Lisp_Object, ptrdiff_t, Lisp_Object *, char *);
extern Lisp_Object larger_vector (Lisp_Object, ptrdiff_t, ptrdiff_t);
extern void sweep_weak_hash_tables (void);
extern char *extract_data_from_object (Lisp_Object, ptrdiff_t *, ptrdiff_t *);
diff --git a/test/src/json-tests.el b/test/src/json-tests.el
index e394583bc7..59b838f27b 100644
--- a/test/src/json-tests.el
+++ b/test/src/json-tests.el
@@ -64,6 +64,16 @@
(should-error (json-serialize '#1=((a . 1) . #1#)) :type 'circular-list)
(should-error (json-serialize '(#1=(a #1#)))))
+(ert-deftest json-serialize/json-false ()
+ (skip-unless (fboundp 'json-serialize))
+ (should (equal (json-serialize '((a . nil)) :json-false nil) "{\"a\":false}"))
+ (should (equal (json-serialize '((a . foo)) :json-false 'foo) "{\"a\":false}")))
+
+(ert-deftest json-serialize/json-null ()
+ (skip-unless (fboundp 'json-serialize))
+ (should (equal (json-serialize '((a . nil)) :json-null nil) "{\"a\":null}"))
+ (should (equal (json-serialize '((a . foo)) :json-null 'foo) "{\"a\":null}")))
+
(ert-deftest json-serialize/object-with-duplicate-keys ()
(skip-unless (fboundp 'json-serialize))
(let ((table (make-hash-table :test #'eq)))
@@ -96,6 +106,24 @@
;; FIXME: Is this the right behavior?
(should (equal (json-parse-string "[\"\u00C4\xC3\x84\"]") ["\u00C4\u00C4"])))
+(ert-deftest json-parse-string/json-false ()
+ (skip-unless (fboundp 'json-parse-string))
+ (let* ((input "{ \"abc\" : [1, 2, true], \"def\" : null, \"abc\" : [9, false] }\n")
+ (json-false-nil (json-parse-string input :json-false nil))
+ (json-false-foo (json-parse-string input :json-false 'foo)))
+ (should (eq (aref (gethash "abc" json-false-nil 'wrong) 1) nil))
+ (should (eq (gethash "def" json-false-nil 'wrong) :null))
+ (should (eq (aref (gethash "abc" json-false-foo 'wrong) 1) 'foo))))
+
+(ert-deftest json-parse-string/json-null ()
+ (skip-unless (fboundp 'json-parse-string))
+ (let* ((input "{ \"abc\" : [1, 2, true], \"def\" : null, \"abc\" : [9, false] }\n")
+ (json-null-nil (json-parse-string input :json-null nil))
+ (json-null-foo (json-parse-string input :json-null 'foo)))
+ (should (null (gethash "def" json-null-nil 'wrong)))
+ (should (eq (aref (gethash "abc" json-null-nil 'wrong) 1) :false))
+ (should (eq (gethash "def" json-null-foo 'wrong) 'foo))))
+
(ert-deftest json-serialize/string ()
(skip-unless (fboundp 'json-serialize))
(should (equal (json-serialize ["foo"]) "[\"foo\"]"))
--
2.15.1
reply other threads:[~2017-12-30 20:07 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20171230200752.19371-1-vibhavp@gmail.com \
--to=vibhavp@gmail.com \
--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 public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).