]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
json: add json_variant_compare() helper for comparint two json variants by order
authorLennart Poettering <lennart@amutable.com>
Wed, 18 Feb 2026 12:45:22 +0000 (13:45 +0100)
committerLennart Poettering <lennart@amutable.com>
Fri, 20 Feb 2026 07:15:29 +0000 (08:15 +0100)
src/libsystemd/sd-json/json-util.c
src/libsystemd/sd-json/json-util.h
src/test/test-json.c

index 41ac8c40976f72425a2ca045f974e2a64d9039f6..7f90b7fc7930cade47b864b4ff0b7fe7db0fa454 100644 (file)
@@ -743,3 +743,105 @@ int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_jso
 
         return 0;
 }
+
+int json_variant_compare(sd_json_variant *a, sd_json_variant *b) {
+        int r;
+
+        r = CMP(!!a, !!b);
+        if (r != 0)
+                return r;
+
+        if (sd_json_variant_equal(a, b))
+                return 0;
+
+        if (sd_json_variant_is_null(a))
+                return -1;
+        if (sd_json_variant_is_null(b))
+                return 1;
+
+        if (sd_json_variant_is_string(a) &&
+            sd_json_variant_is_string(b))
+                return strcmp(sd_json_variant_string(a), sd_json_variant_string(b));
+
+        if (sd_json_variant_is_integer(a) &&
+            sd_json_variant_is_integer(b))
+                return CMP(sd_json_variant_integer(a), sd_json_variant_integer(b));
+
+        if (sd_json_variant_is_unsigned(a) &&
+            sd_json_variant_is_unsigned(b))
+                return CMP(sd_json_variant_unsigned(a), sd_json_variant_unsigned(b));
+
+        /* We cannot necessarily compare 64bit signed with unsigned, hence we go via sign checking instead */
+        if (sd_json_variant_is_number(a) && sd_json_variant_is_number(b)) {
+                if (sd_json_variant_is_negative(a) &&
+                    !sd_json_variant_is_negative(b))
+                        return -1;
+
+                if (!sd_json_variant_is_negative(a) &&
+                    sd_json_variant_is_negative(b))
+                        return 1;
+        }
+
+        if (sd_json_variant_is_real(a) &&
+            sd_json_variant_is_real(b))
+                return CMP(sd_json_variant_real(a), sd_json_variant_real(b));
+
+        if (sd_json_variant_is_boolean(a) &&
+            sd_json_variant_is_boolean(b))
+                return CMP(sd_json_variant_boolean(a), sd_json_variant_boolean(b));
+
+        if (sd_json_variant_is_array(a) &&
+            sd_json_variant_is_array(b)) {
+
+                size_t n = sd_json_variant_elements(a),
+                        m = sd_json_variant_elements(b);
+                for (size_t i = 0; i < n || i < m; i++) {
+
+                        if (i >= n)
+                                return -1;
+                        if (i >= m)
+                                return 1;
+
+                        r = json_variant_compare(
+                                        sd_json_variant_by_index(a, i),
+                                        sd_json_variant_by_index(b, i));
+                        if (r != 0)
+                                return r;
+                }
+
+                return 0;
+        }
+
+        if (sd_json_variant_is_object(a) &&
+            sd_json_variant_is_object(b)) {
+                const char *k, *lowest = NULL;
+                sd_json_variant *v;
+                int result = 0;
+
+                JSON_VARIANT_OBJECT_FOREACH(k, v, a) {
+                        if (lowest && strcmp(k, lowest) >= 0)
+                                continue;
+
+                        r = json_variant_compare(v, sd_json_variant_by_key(b, k));
+                        if (r != 0) {
+                                lowest = k;
+                                result = r;
+                        }
+                }
+
+                JSON_VARIANT_OBJECT_FOREACH(k, v, b) {
+                        if (lowest && strcmp(k, lowest) >= 0)
+                                continue;
+
+                        r = json_variant_compare(v, sd_json_variant_by_key(a, k));
+                        if (r != 0) {
+                                lowest = k;
+                                result = -r;
+                        }
+                }
+
+                return result;
+        }
+
+        return CMP(sd_json_variant_type(a), sd_json_variant_type(b));
+}
index d06a72aef9af38baa726760f8ce487ccdf71aea5..847725a41e292cd800a3716265b81d64e832b4b5 100644 (file)
@@ -273,3 +273,5 @@ int json_variant_new_fd_info(sd_json_variant **ret, int fd);
 
 char *json_underscorify(char *p);
 char *json_dashify(char *p);
+
+int json_variant_compare(sd_json_variant *a, sd_json_variant *b);
index 1f0e07d7d4da3d8b4217981c1383a7fe2d7ca6a0..679152fd955a8aff70bb592ae2ed10cc9da3312a 100644 (file)
@@ -1523,4 +1523,69 @@ TEST(access_mode) {
                                   &mm), ERANGE);
 }
 
+static void test_json_variant_compare_one(const char *a, const char *b, int expected) {
+        int r;
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *aa = NULL;
+        if (!isempty(a))
+                ASSERT_OK(sd_json_parse(a, /* flags= */ 0, &aa, /* reterr_line= */ NULL, /* reterr_column= */ NULL));
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *bb = NULL;
+        if (!isempty(b))
+                ASSERT_OK(sd_json_parse(b, /* flags= */ 0, &bb, /* reterr_line= */ NULL, /* reterr_column= */ NULL));
+
+        r = json_variant_compare(aa, bb);
+
+        log_debug("%s vs %s → %i (expected %i)", a, b, r, expected);
+
+        if (expected < 0)
+                ASSERT_LT(r, 0);
+        else if (expected > 0)
+                ASSERT_GT(r, 0);
+        else
+                ASSERT_EQ(r, 0);
+
+        r = json_variant_compare(bb, aa);
+
+        if (expected < 0)
+                ASSERT_GT(r, 0);
+        else if (expected > 0)
+                ASSERT_LT(r, 0);
+        else
+                ASSERT_EQ(r, 0);
+}
+
+TEST(json_variant_compare) {
+        test_json_variant_compare_one("null", "\"a\"", -1);
+        test_json_variant_compare_one(NULL, "\"a\"", -1);
+        test_json_variant_compare_one("0", "1", -1);
+        test_json_variant_compare_one("1", "0", 1);
+        test_json_variant_compare_one("0", "0", 0);
+        test_json_variant_compare_one("1", "1", 0);
+        test_json_variant_compare_one("1", "null", 1);
+        test_json_variant_compare_one("null", "1", -1);
+        test_json_variant_compare_one("null", "null", 0);
+        test_json_variant_compare_one("false", "true", -1);
+        test_json_variant_compare_one("true", "false", 1);
+        test_json_variant_compare_one("true", "true", 0);
+        test_json_variant_compare_one("false", "false", 0);
+        test_json_variant_compare_one("\"a\"", "\"b\"", -1);
+        test_json_variant_compare_one("\"b\"", "\"a\"", 1);
+        test_json_variant_compare_one("18446744073709551615", "0", 1);
+        test_json_variant_compare_one("0", "18446744073709551615", -1);
+        test_json_variant_compare_one("18446744073709551615", "18446744073709551615", 0);
+        test_json_variant_compare_one("-9223372036854775808", "18446744073709551615", -1);
+        test_json_variant_compare_one("18446744073709551615", "-9223372036854775808", 1);
+        test_json_variant_compare_one("1.1", "3.4", -1);
+        test_json_variant_compare_one("1", "3.4", -1);
+        test_json_variant_compare_one("[1,2]", "[1,2]", 0);
+        test_json_variant_compare_one("[1,2]", "[2,1]", -1);
+        test_json_variant_compare_one("[1,2]", "[1,2,3]", -1);
+        test_json_variant_compare_one("{}", "{\"a\":\"b\"}", -1);
+        test_json_variant_compare_one("{\"a\":\"b\"}", "{\"a\":\"b\"}", 0);
+        test_json_variant_compare_one("{\"a\":\"b\"}", "{\"b\":\"c\"}", 1);
+        test_json_variant_compare_one("{\"a\":\"b\",\"b\":\"c\"}", "{\"b\":\"c\",\"a\":\"b\"}", 0);
+        test_json_variant_compare_one("{\"a\":\"b\",\"b\":\"c\"}", "{\"a\":\"b\"}", 1);
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);