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 retobj = opaque;
virJSONValuePtr newval = NULL; virJSONValuePtr newval = NULL;
const char *newkey; virJSONValuePtr existobj;
char **tokens = NULL;
size_t ntokens = 0;
int ret = -1;
if (!(newkey = STRSKIP(key, "file."))) { /* non-nested keys only need to be copied */
virReportError(VIR_ERR_INVALID_ARG, "%s", if (!strchr(key, '.')) {
_("JSON object is neither nested nor flattened")); if (!(newval = virJSONValueCopy(value)))
return -1; 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))) if (!(tokens = virStringSplitCount(key, ".", 2, &ntokens)))
return -1; goto cleanup;
if (virJSONValueObjectAppend(retobj, newkey, newval) < 0) { if (ntokens != 2) {
virJSONValueFree(newval); virReportError(VIR_ERR_INVALID_ARG,
return -1; _("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 * 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 * hierarchy so that the parsers can be kept simple and we still can use the
* weird syntax some users might use. * 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 virJSONValuePtr
virJSONValueObjectDeflatten(virJSONValuePtr json) virJSONValueObjectDeflatten(virJSONValuePtr json)
@ -2023,10 +2057,7 @@ virJSONValueObjectDeflatten(virJSONValuePtr json)
deflattened) < 0) deflattened) < 0)
goto cleanup; goto cleanup;
if (virJSONValueObjectCreate(&ret, "a:file", deflattened, NULL) < 0) VIR_STEAL_PTR(ret, deflattened);
goto cleanup;
deflattened = NULL;
cleanup: cleanup:
virJSONValueFree(deflattened); 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": { "file": {
"nest": { "nest": {
"into": 2,
}, "there": "too"
"nest.into": 2, }
"nest.there": "too"
} }
} }

View File

@ -1,11 +1,23 @@
{ {
"file": { "file": {
"double.nest1": "some", "double": {
"double.nest2": "more", "nest1": "some",
"double.nest3": { "nest2": "more",
"even": "objects" "nest3": {
"even": "objects"
}
}, },
"very.deeply.nested.object.chains.nest1": "some", "very": {
"very.deeply.nested.object.chains.nest2": "stuff" "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("unflattened", true);
DO_TEST_DEFLATTEN("basic-file", 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-file", true);
DO_TEST_DEFLATTEN("deep-generic", false); DO_TEST_DEFLATTEN("deep-generic", true);
DO_TEST_DEFLATTEN("nested", 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", true);
DO_TEST_DEFLATTEN("concat-double-key", true); DO_TEST_DEFLATTEN("concat-double-key", false);
return (ret == 0) ? EXIT_SUCCESS : EXIT_FAILURE; return (ret == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
} }