summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <withnall@endlessm.com>2020-06-09 15:41:45 +0100
committerPhilip Withnall <withnall@endlessm.com>2020-06-09 15:43:04 +0100
commitdd7a711244e3d33e3e960cd990e9afccc6877820 (patch)
tree6cdd86fb2578dee5f5626091cdcbd78a6c45df0a
parent13142637c8132f49e1bd012786eb5f7f7c6dc986 (diff)
downloadjson-glib-dd7a711244e3d33e3e960cd990e9afccc6877820.tar.gz
json-parser: Support loading files via memory mapping
Add a new `json_parser_load_from_mapped_file()` to load JSON from files via memory mapping. It’s otherwise similar to `json_parser_load_from_file()`. It’s in the right position to be able to memory map the file it’s reading from: it reads the input once before building a `JsonNode` structure to represent it, doesn’t write to the file, and often deals with large input files. This should speed things up slightly due to reducing time spent allocating a large chunk of heap memory to load the file into, if a caller can support that. Signed-off-by: Philip Withnall <withnall@endlessm.com>
-rw-r--r--doc/json-glib-sections.txt1
-rw-r--r--json-glib/json-parser.c58
-rw-r--r--json-glib/json-parser.h4
-rw-r--r--json-glib/tests/invalid.json1
-rw-r--r--json-glib/tests/meson.build1
-rw-r--r--json-glib/tests/parser.c56
6 files changed, 121 insertions, 0 deletions
diff --git a/doc/json-glib-sections.txt b/doc/json-glib-sections.txt
index ada0895..ced06d0 100644
--- a/doc/json-glib-sections.txt
+++ b/doc/json-glib-sections.txt
@@ -183,6 +183,7 @@ JsonParserClass
json_parser_new
json_parser_new_immutable
json_parser_load_from_file
+json_parser_load_from_mapped_file
json_parser_load_from_data
json_parser_load_from_stream
json_parser_load_from_stream_async
diff --git a/json-glib/json-parser.c b/json-glib/json-parser.c
index a90bbd8..3d77975 100644
--- a/json-glib/json-parser.c
+++ b/json-glib/json-parser.c
@@ -1089,6 +1089,9 @@ json_parser_load (JsonParser *parser,
* Loads a JSON stream from the content of @filename and parses it. See
* json_parser_load_from_data().
*
+ * If the file is large or shared between processes,
+ * json_parser_load_from_mapped_file() may be a more efficient way to load it.
+ *
* Return value: %TRUE if the file was successfully loaded and parsed.
* In case of error, @error is set accordingly and %FALSE is returned
*/
@@ -1132,6 +1135,61 @@ json_parser_load_from_file (JsonParser *parser,
}
/**
+ * json_parser_load_from_mapped_file:
+ * @parser: a #JsonParser
+ * @filename: the path for the file to parse
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads a JSON stream from the content of @filename and parses it. Unlike
+ * json_parser_load_from_file(), @filename will be memory mapped as read-only
+ * and parsed. @filename will be unmapped before this function returns.
+ *
+ * If mapping or reading the file fails, a %G_FILE_ERROR will be returned.
+ *
+ * Return value: %TRUE if the file was successfully loaded and parsed.
+ * In case of error, @error is set accordingly and %FALSE is returned
+ * Since: 1.6
+ */
+gboolean
+json_parser_load_from_mapped_file (JsonParser *parser,
+ const gchar *filename,
+ GError **error)
+{
+ JsonParserPrivate *priv;
+ GError *internal_error = NULL;
+ gboolean retval = TRUE;
+ GMappedFile *mapped_file = NULL;
+
+ g_return_val_if_fail (JSON_IS_PARSER (parser), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ priv = parser->priv;
+
+ mapped_file = g_mapped_file_new (filename, FALSE, &internal_error);
+ if (mapped_file == NULL)
+ {
+ g_propagate_error (error, internal_error);
+ return FALSE;
+ }
+
+ g_free (priv->filename);
+
+ priv->is_filename = TRUE;
+ priv->filename = g_strdup (filename);
+
+ if (!json_parser_load (parser, g_mapped_file_get_contents (mapped_file),
+ g_mapped_file_get_length (mapped_file), &internal_error))
+ {
+ g_propagate_error (error, internal_error);
+ retval = FALSE;
+ }
+
+ g_clear_pointer (&mapped_file, g_mapped_file_unref);
+
+ return retval;
+}
+
+/**
* json_parser_load_from_data:
* @parser: a #JsonParser
* @data: the buffer to parse
diff --git a/json-glib/json-parser.h b/json-glib/json-parser.h
index 1470fbf..91fde66 100644
--- a/json-glib/json-parser.h
+++ b/json-glib/json-parser.h
@@ -153,6 +153,10 @@ JSON_AVAILABLE_IN_1_0
gboolean json_parser_load_from_file (JsonParser *parser,
const gchar *filename,
GError **error);
+JSON_AVAILABLE_IN_1_6
+gboolean json_parser_load_from_mapped_file (JsonParser *parser,
+ const gchar *filename,
+ GError **error);
JSON_AVAILABLE_IN_1_0
gboolean json_parser_load_from_data (JsonParser *parser,
const gchar *data,
diff --git a/json-glib/tests/invalid.json b/json-glib/tests/invalid.json
new file mode 100644
index 0000000..1634764
--- /dev/null
+++ b/json-glib/tests/invalid.json
@@ -0,0 +1 @@
+nope
diff --git a/json-glib/tests/meson.build b/json-glib/tests/meson.build
index 881d902..7fdbc3f 100644
--- a/json-glib/tests/meson.build
+++ b/json-glib/tests/meson.build
@@ -16,6 +16,7 @@ tests = [
]
test_data = [
+ 'invalid.json',
'stream-load.json',
]
diff --git a/json-glib/tests/parser.c b/json-glib/tests/parser.c
index acc6276..ddec577 100644
--- a/json-glib/tests/parser.c
+++ b/json-glib/tests/parser.c
@@ -754,6 +754,59 @@ test_stream_async (void)
g_free (path);
}
+/* Test json_parser_load_from_mapped_file() succeeds. */
+static void
+test_mapped (void)
+{
+ GError *error = NULL;
+ JsonParser *parser = json_parser_new ();
+ char *path;
+
+ path = g_test_build_filename (G_TEST_DIST, "stream-load.json", NULL);
+
+ json_parser_load_from_mapped_file (parser, path, &error);
+ g_assert_no_error (error);
+
+ assert_stream_load_json_correct (parser);
+
+ g_object_unref (parser);
+ g_free (path);
+}
+
+/* Test json_parser_load_from_mapped_file() error handling for file I/O. */
+static void
+test_mapped_file_error (void)
+{
+ GError *error = NULL;
+ JsonParser *parser = json_parser_new ();
+
+ json_parser_load_from_mapped_file (parser, "nope.json", &error);
+ g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
+
+ g_assert_null (json_parser_get_root (parser));
+
+ g_object_unref (parser);
+}
+
+/* Test json_parser_load_from_mapped_file() error handling for JSON parsing. */
+static void
+test_mapped_json_error (void)
+{
+ GError *error = NULL;
+ JsonParser *parser = json_parser_new ();
+ char *path;
+
+ path = g_test_build_filename (G_TEST_DIST, "invalid.json", NULL);
+
+ json_parser_load_from_mapped_file (parser, path, &error);
+ g_assert_error (error, JSON_PARSER_ERROR, JSON_PARSER_ERROR_INVALID_BAREWORD);
+
+ g_assert_null (json_parser_get_root (parser));
+
+ g_object_unref (parser);
+ g_free (path);
+}
+
int
main (int argc,
char *argv[])
@@ -772,6 +825,9 @@ main (int argc,
g_test_add_func ("/parser/unicode-escape", test_unicode_escape);
g_test_add_func ("/parser/stream-sync", test_stream_sync);
g_test_add_func ("/parser/stream-async", test_stream_async);
+ g_test_add_func ("/parser/mapped", test_mapped);
+ g_test_add_func ("/parser/mapped/file-error", test_mapped_file_error);
+ g_test_add_func ("/parser/mapped/json-error", test_mapped_json_error);
return g_test_run ();
}