On Wed, Aug 14, 2024 at 23:40:22 +0200, Ján Tomko wrote:
> Write an alternative implementation of our virJSON functions,
> using json-c instead of yajl.
>
> Signed-off-by: Ján Tomko <jtomko@redhat.com>
> ---
> src/util/virjson.c | 177 ++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 175 insertions(+), 2 deletions(-)
>
> diff --git a/src/util/virjson.c b/src/util/virjson.c
> index 0edf86cd1c..7a22c54f03 100644
> --- a/src/util/virjson.c
> +++ b/src/util/virjson.c
[...]
> @@ -1389,8 +1391,179 @@ virJSONValueCopy(const virJSONValue *in)
> return out;
> }
>
Two lines.
> +#if WITH_JSON_C
> +static virJSONValue *
> +virJSONValueFromJsonC(json_object *jobj)
Hmm, I wonder how much feasible is it to rewrite the helpers to avoid
this conversion in the future.
> +{
> + enum json_type type = json_object_get_type(jobj);
> + virJSONValue *ret = NULL;
>
> -#if WITH_YAJL
> + switch (type) {
> + case json_type_null:
> + ret = virJSONValueNewNull();
> + break;
> + case json_type_boolean:
> + ret = virJSONValueNewBoolean(json_object_get_boolean(jobj));
> + break;
> + case json_type_double:
> + case json_type_int: {
> + ret = virJSONValueNewNumber(g_strdup(json_object_get_string(jobj)));
> + break;
> + }
> + case json_type_object:
> + ret = virJSONValueNewObject();
> + {
This block-in-case style is very punk.
> + json_object_object_foreach(jobj, key, val) {
> + virJSONValue *cur = virJSONValueFromJsonC(val);
> +
> + if (virJSONValueObjectAppend(ret, key, &cur) < 0) {
> + g_free(ret);
> + return NULL;
> + }
> + }
> + }
> + break;
> + case json_type_array: {
> + size_t len;
> + size_t i;
> +
> + ret = virJSONValueNewArray();
> + len = json_object_array_length(jobj);
> +
> + for (i = 0; i < len; i++) {
> + virJSONValue *cur = NULL;
> + json_object *val = NULL;
> +
> + val = json_object_array_get_idx(jobj, i);
> +
> + cur = virJSONValueFromJsonC(val);
> +
> + virJSONValueArrayAppend(ret, &cur);
> + }
> + break;
> + }
> + case json_type_string:
> + ret = virJSONValueNewString(g_strdup(json_object_get_string(jobj)));
> + break;
> + }
> +
> + VIR_DEBUG("ret=%p", ret);
I doubt this is useful. Either drop it or print at least the 'type'
field.
> + return ret;
> +}
> +
> +virJSONValue *
Two lines.
> +virJSONValueFromString(const char *jsonstring)
> +{
> + json_object *jobj = NULL;
> + virJSONValue *ret = NULL;
> + json_tokener *tok = NULL;
> + enum json_tokener_error jerr;
> + int jsonflags = JSON_TOKENER_STRICT | JSON_TOKENER_VALIDATE_UTF8;
> +
> + VIR_DEBUG("string=%s", jsonstring);
> +
> + tok = json_tokener_new();
> + json_tokener_set_flags(tok, jsonflags);
> + jobj = json_tokener_parse_ex(tok, jsonstring, strlen(jsonstring));
> + jerr = json_tokener_get_error(tok);
> + if (jerr != json_tokener_success) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + "%s", json_tokener_error_desc(jerr));
> + goto cleanup;
> + }
> + ret = virJSONValueFromJsonC(jobj);
> +
> + cleanup:
> + VIR_DEBUG("result=%p", ret);
So this effectively will always be a pointer except when the
json_tokener_parse_ex returned error, but in that case we'd already
report an error. Is it really needed?
> + json_object_put(jobj);
> + json_tokener_free(tok);
> + return ret;
> +}
> +
> +static json_object *
> +virJSONValueToJsonC(virJSONValue *object)
> +{
> + json_object *ret = NULL;
> + size_t i;
> +
> + VIR_DEBUG("object=%p type=%d", object, object->type);
^^ this is imo more useful.
> +
> + switch (object->type) {
> + case VIR_JSON_TYPE_OBJECT:
> + // constant key?
c99 comment.
> + ret = json_object_new_object();
> + for (i = 0; i < object->data.object.npairs; i++) {
> + json_object *child = virJSONValueToJsonC(object->data.object.pairs[i].value);
> + json_object_object_add(ret, object->data.object.pairs[i].key, child);
> + }
> + return ret;
> + case VIR_JSON_TYPE_ARRAY:
> + /* json_object_new_array_ext was introduced in json-c 0.16 */
> +# if JSON_C_VERSION_NUM < ((0 << 16) | (16 << 8))
> + ret = json_object_new_array();
> +# else
> + ret = json_object_new_array_ext(object->data.array.nvalues);
> +# endif
> + for (i = 0; i < object->data.array.nvalues; i++) {
> + json_object_array_add(ret,
> + virJSONValueToJsonC(object->data.array.values[i]));
> + }
> + return ret;
> +
> + case VIR_JSON_TYPE_STRING:
> + return json_object_new_string(object->data.string);
> +
> + case VIR_JSON_TYPE_NUMBER: {
> + /* Yes. That's a random value. We only care about the string. */
> + return json_object_new_double_s(299792458, object->data.number);
oof. Shouldn't we at least attempt to format the number into a double?
I understand that you're using this to allow for the exact
representation and that a cannary value might ease debugging but this
looks a bit weird. At least add a comment explaining that it's a cannary
and a hack so that json-c will use our representation.
> + }
> + case VIR_JSON_TYPE_BOOLEAN:
> + return json_object_new_boolean(object->data.boolean);
> +
> + case VIR_JSON_TYPE_NULL:
> + default:
> + return NULL;
> + }
> + return NULL;
> +}
> +
> +
> +int
> +virJSONValueToBuffer(virJSONValue *object,
> + virBuffer *buf,
> + bool pretty)
> +{
> + json_object *jobj = NULL;
> + const char *str;
> + size_t len;
> + int ret = -1;
> + int jsonflags = JSON_C_TO_STRING_NOSLASHESCAPE;
> +
> + VIR_DEBUG("object=%p", object);
> +
> + if (pretty)
> + jsonflags |= JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED;
> +
> + jobj = virJSONValueToJsonC(object);
> + if (!jobj) {
So here you check the return value of 'virJSONValueToJsonC' but none of
the other calls in this function do that.
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("failed to convert virJSONValue to json-c data"));
> + goto cleanup;
> + }
> +
> + str = json_object_to_json_string_length(jobj, jsonflags, &len);
> +
> + virBufferAdd(buf, str, len);
> + if (pretty)
> + virBufferAddLit(buf, "\n");
> + ret = 0;
> +
> + cleanup:
> + json_object_put(jobj);
> + return ret;
> +}
> +
Two lines.
> +#elif WITH_YAJL
Looks good, but I want to understand the error reporting discrepancy
I've pointed out above before giving r-b.