diff options
author | Philip Withnall <withnall@endlessm.com> | 2020-06-09 15:41:45 +0100 |
---|---|---|
committer | Philip Withnall <withnall@endlessm.com> | 2020-06-09 15:43:04 +0100 |
commit | dd7a711244e3d33e3e960cd990e9afccc6877820 (patch) | |
tree | 6cdd86fb2578dee5f5626091cdcbd78a6c45df0a | |
parent | 13142637c8132f49e1bd012786eb5f7f7c6dc986 (diff) | |
download | json-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.txt | 1 | ||||
-rw-r--r-- | json-glib/json-parser.c | 58 | ||||
-rw-r--r-- | json-glib/json-parser.h | 4 | ||||
-rw-r--r-- | json-glib/tests/invalid.json | 1 | ||||
-rw-r--r-- | json-glib/tests/meson.build | 1 | ||||
-rw-r--r-- | json-glib/tests/parser.c | 56 |
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 (); } |