From d40f4b3e673cda123644c32bc815e7e2f1fc8fcd Mon Sep 17 00:00:00 2001 From: Peter Krempa Date: Tue, 27 Jun 2017 13:48:56 +0200 Subject: [PATCH] util: json: Properly implement JSON deflattening As it turns out sometimes users pass in an arbitrarily nested structure e.g. for the qemu backing chains JSON pseudo protocol. This new implementation deflattens now a single object fully even with nested keys. Additionally it's not necessary now to stick with the "file." prefix for the properties. --- src/util/virjson.c | 67 ++++++++++++++----- .../deflatten-basic-generic-out.json | 20 ++++++ .../deflatten-concat-double-key-out.json | 9 --- tests/virjsondata/deflatten-concat-out.json | 7 +- .../virjsondata/deflatten-deep-file-out.json | 24 +++++-- .../deflatten-deep-generic-out.json | 27 ++++++++ .../virjsondata/deflatten-double-key-out.json | 6 -- tests/virjsontest.c | 8 +-- 8 files changed, 121 insertions(+), 47 deletions(-) create mode 100644 tests/virjsondata/deflatten-basic-generic-out.json delete mode 100644 tests/virjsondata/deflatten-concat-double-key-out.json create mode 100644 tests/virjsondata/deflatten-deep-generic-out.json delete mode 100644 tests/virjsondata/deflatten-double-key-out.json diff --git a/src/util/virjson.c b/src/util/virjson.c index d55913a143..665278b0aa 100644 --- a/src/util/virjson.c +++ b/src/util/virjson.c @@ -1974,23 +1974,60 @@ virJSONValueObjectDeflattenWorker(const char *key, { virJSONValuePtr retobj = opaque; virJSONValuePtr newval = NULL; - const char *newkey; + virJSONValuePtr existobj; + char **tokens = NULL; + size_t ntokens = 0; + int ret = -1; - if (!(newkey = STRSKIP(key, "file."))) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("JSON object is neither nested nor flattened")); - return -1; + /* non-nested keys only need to be copied */ + if (!strchr(key, '.')) { + if (!(newval = virJSONValueCopy(value))) + return -1; + + if (virJSONValueObjectHasKey(retobj, key)) { + virReportError(VIR_ERR_INVALID_ARG, + _("can't deflatten colliding key '%s'"), key); + goto cleanup; + } + + if (virJSONValueObjectAppend(retobj, key, newval) < 0) + goto cleanup; + + return 0; } - if (!(newval = virJSONValueCopy(value))) - return -1; + if (!(tokens = virStringSplitCount(key, ".", 2, &ntokens))) + goto cleanup; - if (virJSONValueObjectAppend(retobj, newkey, newval) < 0) { - virJSONValueFree(newval); - return -1; + if (ntokens != 2) { + virReportError(VIR_ERR_INVALID_ARG, + _("invalid nested value key '%s'"), key); + goto cleanup; } - return 0; + if (!(existobj = virJSONValueObjectGet(retobj, tokens[0]))) { + if (!(existobj = virJSONValueNewObject())) + goto cleanup; + + if (virJSONValueObjectAppend(retobj, tokens[0], existobj) < 0) + goto cleanup; + + } else { + if (!virJSONValueIsObject(existobj)) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("mixing nested objects and values is forbidden in " + "JSON deflattening")); + goto cleanup; + } + } + + ret = virJSONValueObjectDeflattenWorker(tokens[1], value, existobj); + + cleanup: + virStringListFreeCount(tokens, ntokens); + virJSONValueFree(newval); + + return ret; } @@ -2005,9 +2042,6 @@ virJSONValueObjectDeflattenWorker(const char *key, * This function will attempt to reverse the process and provide a nested json * hierarchy so that the parsers can be kept simple and we still can use the * weird syntax some users might use. - * - * Currently this function will flatten out just the 'file.' prefix into a new - * tree. Any other syntax will be rejected. */ virJSONValuePtr virJSONValueObjectDeflatten(virJSONValuePtr json) @@ -2023,10 +2057,7 @@ virJSONValueObjectDeflatten(virJSONValuePtr json) deflattened) < 0) goto cleanup; - if (virJSONValueObjectCreate(&ret, "a:file", deflattened, NULL) < 0) - goto cleanup; - - deflattened = NULL; + VIR_STEAL_PTR(ret, deflattened); cleanup: virJSONValueFree(deflattened); diff --git a/tests/virjsondata/deflatten-basic-generic-out.json b/tests/virjsondata/deflatten-basic-generic-out.json new file mode 100644 index 0000000000..ab639aa485 --- /dev/null +++ b/tests/virjsondata/deflatten-basic-generic-out.json @@ -0,0 +1,20 @@ +{ + "foo": { + "int": 1, + "string": "string", + "object": { + "data": "value", + "foo": "bar" + } + }, + "bar": { + "int": 1 + }, + "blurb": { + "string": "string", + "object": { + "data": "value", + "foo": "bar" + } + } +} diff --git a/tests/virjsondata/deflatten-concat-double-key-out.json b/tests/virjsondata/deflatten-concat-double-key-out.json deleted file mode 100644 index 5624ef123f..0000000000 --- a/tests/virjsondata/deflatten-concat-double-key-out.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "file": { - "nest": { - "into": "is already here" - }, - "nest.into": 2, - "nest.there": "too" - } -} diff --git a/tests/virjsondata/deflatten-concat-out.json b/tests/virjsondata/deflatten-concat-out.json index 539d2cc306..be417e53aa 100644 --- a/tests/virjsondata/deflatten-concat-out.json +++ b/tests/virjsondata/deflatten-concat-out.json @@ -1,9 +1,8 @@ { "file": { "nest": { - - }, - "nest.into": 2, - "nest.there": "too" + "into": 2, + "there": "too" + } } } diff --git a/tests/virjsondata/deflatten-deep-file-out.json b/tests/virjsondata/deflatten-deep-file-out.json index a5910c9f72..d4614eeaf9 100644 --- a/tests/virjsondata/deflatten-deep-file-out.json +++ b/tests/virjsondata/deflatten-deep-file-out.json @@ -1,11 +1,23 @@ { "file": { - "double.nest1": "some", - "double.nest2": "more", - "double.nest3": { - "even": "objects" + "double": { + "nest1": "some", + "nest2": "more", + "nest3": { + "even": "objects" + } }, - "very.deeply.nested.object.chains.nest1": "some", - "very.deeply.nested.object.chains.nest2": "stuff" + "very": { + "deeply": { + "nested": { + "object": { + "chains": { + "nest1": "some", + "nest2": "stuff" + } + } + } + } + } } } diff --git a/tests/virjsondata/deflatten-deep-generic-out.json b/tests/virjsondata/deflatten-deep-generic-out.json new file mode 100644 index 0000000000..7ea521a8f0 --- /dev/null +++ b/tests/virjsondata/deflatten-deep-generic-out.json @@ -0,0 +1,27 @@ +{ + "foo": { + "double": { + "nest1": "some", + "nest2": "more" + }, + "very": { + "deeply": { + "nested": { + "object": { + "chains": { + "nest1": "some", + "nest2": "stuff" + } + } + } + } + } + }, + "bar": { + "double": { + "nest3": { + "even": "objects" + } + } + } +} diff --git a/tests/virjsondata/deflatten-double-key-out.json b/tests/virjsondata/deflatten-double-key-out.json deleted file mode 100644 index ca6766e5b6..0000000000 --- a/tests/virjsondata/deflatten-double-key-out.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "file": { - "nest": 1, - "nest.into": 2 - } -} diff --git a/tests/virjsontest.c b/tests/virjsontest.c index e8c4e75f4a..2771cb5cd8 100644 --- a/tests/virjsontest.c +++ b/tests/virjsontest.c @@ -512,13 +512,13 @@ mymain(void) DO_TEST_DEFLATTEN("unflattened", true); DO_TEST_DEFLATTEN("basic-file", true); - DO_TEST_DEFLATTEN("basic-generic", false); + DO_TEST_DEFLATTEN("basic-generic", true); DO_TEST_DEFLATTEN("deep-file", true); - DO_TEST_DEFLATTEN("deep-generic", false); + DO_TEST_DEFLATTEN("deep-generic", true); DO_TEST_DEFLATTEN("nested", true); - DO_TEST_DEFLATTEN("double-key", true); + DO_TEST_DEFLATTEN("double-key", false); DO_TEST_DEFLATTEN("concat", true); - DO_TEST_DEFLATTEN("concat-double-key", true); + DO_TEST_DEFLATTEN("concat-double-key", false); return (ret == 0) ? EXIT_SUCCESS : EXIT_FAILURE; }