json: teach json builder "conditional" object fields
authorLennart Poettering <lennart@poettering.net>
Tue, 27 Nov 2018 21:26:21 +0000 (22:26 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 28 Nov 2018 07:38:55 +0000 (08:38 +0100)
Quite often when we generate objects some fields should only be
generated in some conditions. Let's add high-level support for that.
Matching the existing JSON_BUILD_PAIR() this adds
JSON_BUILD_PAIR_CONDITIONAL() which is very similar, but takes an
additional parameter: a boolean condition. If "true" this acts like
JSON_BUILD_PAIR(), but if false then the whole pair is suppressed.

This sounds simply, but requires a tiny bit of complexity: when complex
sub-variants are used in fields, then we also need to suppress them.

src/shared/json.c
src/shared/json.h
src/test/test-json.c

index eec6ea7..420555a 100644 (file)
@@ -2249,6 +2249,7 @@ typedef struct JsonStack {
         size_t n_elements, n_elements_allocated;
         unsigned line_before;
         unsigned column_before;
+        size_t n_suppress; /* When building: if > 0, suppress this many subsequent elements. If == (size_t) -1, suppress all subsequent elements */
 } JsonStack;
 
 static void json_stack_release(JsonStack *s) {
@@ -2656,6 +2657,8 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
         for (;;) {
                 _cleanup_(json_variant_unrefp) JsonVariant *add = NULL;
+                size_t n_subtract = 0; /* how much to subtract from current->n_suppress, i.e. how many elements would
+                                        * have been added to the current variant */
                 JsonStack *current;
                 int command;
 
@@ -2679,9 +2682,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         p = va_arg(ap, const char *);
 
-                        r = json_variant_new_string(&add, p);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_string(&add, p);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2703,9 +2710,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         j = va_arg(ap, intmax_t);
 
-                        r = json_variant_new_integer(&add, j);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_integer(&add, j);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2727,9 +2738,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         j = va_arg(ap, uintmax_t);
 
-                        r = json_variant_new_unsigned(&add, j);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_unsigned(&add, j);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2751,9 +2766,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         d = va_arg(ap, long double);
 
-                        r = json_variant_new_real(&add, d);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_real(&add, d);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2775,9 +2794,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         b = va_arg(ap, int);
 
-                        r = json_variant_new_boolean(&add, b);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_boolean(&add, b);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2796,9 +2819,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
                                 goto finish;
                         }
 
-                        r = json_variant_new_null(&add);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_null(&add);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2816,12 +2843,16 @@ int json_buildv(JsonVariant **ret, va_list ap) {
                                 goto finish;
                         }
 
+                        /* Note that we don't care for current->n_suppress here, after all the variant is already
+                         * allocated anyway... */
                         add = va_arg(ap, JsonVariant*);
                         if (!add)
                                 add = JSON_VARIANT_MAGIC_NULL;
                         else
                                 json_variant_ref(add);
 
+                        n_subtract = 1;
+
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
                         else if (current->expect == EXPECT_OBJECT_VALUE)
@@ -2841,13 +2872,17 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         l = va_arg(ap, const char *);
 
-                        if (!l)
-                                add = JSON_VARIANT_MAGIC_NULL;
-                        else {
+                        if (l) {
+                                /* Note that we don't care for current->n_suppress here, we should generate parsing
+                                 * errors even in suppressed object properties */
+
                                 r = json_parse(l, &add, NULL, NULL);
                                 if (r < 0)
                                         goto finish;
-                        }
+                        } else
+                                add = JSON_VARIANT_MAGIC_NULL;
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2881,6 +2916,10 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         stack[n_stack++] = (JsonStack) {
                                 .expect = EXPECT_ARRAY_ELEMENT,
+                                .n_suppress = current->n_suppress != 0 ? (size_t) -1 : 0, /* if we shall suppress the
+                                                                                           * new array, then we should
+                                                                                           * also suppress all array
+                                                                                           * members */
                         };
 
                         break;
@@ -2893,9 +2932,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         assert(n_stack > 1);
 
-                        r = json_variant_new_array(&add, current->elements, current->n_elements);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_array(&add, current->elements, current->n_elements);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         json_stack_release(current);
                         n_stack--, current--;
@@ -2912,9 +2955,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         l = va_arg(ap, char **);
 
-                        r = json_variant_new_array_strv(&add, l);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_array_strv(&add, l);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         if (current->expect == EXPECT_TOPLEVEL)
                                 current->expect = EXPECT_END;
@@ -2948,6 +2995,10 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         stack[n_stack++] = (JsonStack) {
                                 .expect = EXPECT_OBJECT_KEY,
+                                .n_suppress = current->n_suppress != 0 ? (size_t) -1 : 0, /* if we shall suppress the
+                                                                                           * new object, then we should
+                                                                                           * also suppress all object
+                                                                                           * members */
                         };
 
                         break;
@@ -2961,9 +3012,13 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         assert(n_stack > 1);
 
-                        r = json_variant_new_object(&add, current->elements, current->n_elements);
-                        if (r < 0)
-                                goto finish;
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_object(&add, current->elements, current->n_elements);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
 
                         json_stack_release(current);
                         n_stack--, current--;
@@ -2980,15 +3035,47 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         n = va_arg(ap, const char *);
 
-                        r = json_variant_new_string(&add, n);
-                        if (r < 0)
+                        if (current->n_suppress == 0) {
+                                r = json_variant_new_string(&add, n);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1;
+
+                        current->expect = EXPECT_OBJECT_VALUE;
+                        break;
+                }
+
+                case _JSON_BUILD_PAIR_CONDITION: {
+                        const char *n;
+                        bool b;
+
+                        if (current->expect != EXPECT_OBJECT_KEY) {
+                                r = -EINVAL;
                                 goto finish;
+                        }
+
+                        b = va_arg(ap, int);
+                        n = va_arg(ap, const char *);
+
+                        if (b && current->n_suppress == 0) {
+                                r = json_variant_new_string(&add, n);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        n_subtract = 1; /* we generated one item */
+
+                        if (!b && current->n_suppress != (size_t) -1)
+                                current->n_suppress += 2; /* Suppress this one and the next item */
 
                         current->expect = EXPECT_OBJECT_VALUE;
                         break;
                 }}
 
-                if (add) {
+                /* If a variant was generated, add it to our current variant, but only if we are not supposed to suppress additions */
+                if (add && current->n_suppress == 0) {
                         if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) {
                                 r = -ENOMEM;
                                 goto finish;
@@ -2996,6 +3083,16 @@ int json_buildv(JsonVariant **ret, va_list ap) {
 
                         current->elements[current->n_elements++] = TAKE_PTR(add);
                 }
+
+                /* If we are supposed to suppress items, let's subtract how many items where generated from that
+                 * counter. Except if the counter is (size_t) -1, i.e. we shall suppress an infinite number of elements
+                 * on this stack level */
+                if (current->n_suppress != (size_t) -1) {
+                        if (current->n_suppress <= n_subtract) /* Saturated */
+                                current->n_suppress = 0;
+                        else
+                                current->n_suppress -= n_subtract;
+                }
         }
 
 done:
index c9482d2..278ff77 100644 (file)
@@ -177,6 +177,7 @@ enum {
         _JSON_BUILD_OBJECT_BEGIN,
         _JSON_BUILD_OBJECT_END,
         _JSON_BUILD_PAIR,
+        _JSON_BUILD_PAIR_CONDITION,
         _JSON_BUILD_NULL,
         _JSON_BUILD_VARIANT,
         _JSON_BUILD_LITERAL,
@@ -192,6 +193,7 @@ enum {
 #define JSON_BUILD_ARRAY(...) _JSON_BUILD_ARRAY_BEGIN, __VA_ARGS__, _JSON_BUILD_ARRAY_END
 #define JSON_BUILD_OBJECT(...) _JSON_BUILD_OBJECT_BEGIN, __VA_ARGS__, _JSON_BUILD_OBJECT_END
 #define JSON_BUILD_PAIR(n, ...) _JSON_BUILD_PAIR, ({ const char *_x = n; _x; }), __VA_ARGS__
+#define JSON_BUILD_PAIR_CONDITION(c, n, ...) _JSON_BUILD_PAIR_CONDITION, ({ bool _x = c; _x; }), ({ const char *_x = n; _x; }), __VA_ARGS__
 #define JSON_BUILD_NULL _JSON_BUILD_NULL
 #define JSON_BUILD_VARIANT(v) _JSON_BUILD_VARIANT, ({ JsonVariant *_x = v; _x; })
 #define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; })
index 6a57f88..d3ece0b 100644 (file)
@@ -316,6 +316,22 @@ static void test_build(void) {
 
         a = json_variant_unref(a);
         b = json_variant_unref(b);
+
+        assert_se(json_build(&a, JSON_BUILD_OBJECT(
+                                             JSON_BUILD_PAIR("x", JSON_BUILD_STRING("y")),
+                                             JSON_BUILD_PAIR("z", JSON_BUILD_STRING("a")),
+                                             JSON_BUILD_PAIR("b", JSON_BUILD_STRING("c"))
+                             )) >= 0);
+
+        assert_se(json_build(&b, JSON_BUILD_OBJECT(
+                                             JSON_BUILD_PAIR("x", JSON_BUILD_STRING("y")),
+                                             JSON_BUILD_PAIR_CONDITION(false, "p", JSON_BUILD_STRING("q")),
+                                             JSON_BUILD_PAIR_CONDITION(true, "z", JSON_BUILD_STRING("a")),
+                                             JSON_BUILD_PAIR_CONDITION(false, "j", JSON_BUILD_ARRAY(JSON_BUILD_STRING("k"), JSON_BUILD_STRING("u"), JSON_BUILD_STRING("i"))),
+                                             JSON_BUILD_PAIR("b", JSON_BUILD_STRING("c"))
+                             )) >= 0);
+
+        assert_se(json_variant_equal(a, b));
 }
 
 static void test_source(void) {