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.
This commit is contained in:
Peter Krempa 2017-06-27 13:48:56 +02:00
parent 7f1209ad1e
commit d40f4b3e67
8 changed files with 121 additions and 47 deletions

View File

@ -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);

View File

@ -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"
}
}
}

View File

@ -1,9 +0,0 @@
{
"file": {
"nest": {
"into": "is already here"
},
"nest.into": 2,
"nest.there": "too"
}
}

View File

@ -1,9 +1,8 @@
{
"file": {
"nest": {
},
"nest.into": 2,
"nest.there": "too"
"into": 2,
"there": "too"
}
}
}

View File

@ -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"
}
}
}
}
}
}
}

View File

@ -0,0 +1,27 @@
{
"foo": {
"double": {
"nest1": "some",
"nest2": "more"
},
"very": {
"deeply": {
"nested": {
"object": {
"chains": {
"nest1": "some",
"nest2": "stuff"
}
}
}
}
}
},
"bar": {
"double": {
"nest3": {
"even": "objects"
}
}
}
}

View File

@ -1,6 +0,0 @@
{
"file": {
"nest": 1,
"nest.into": 2
}
}

View File

@ -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;
}