]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: use json_append() and json_variant_append_array() 28376/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 13 Jul 2023 06:30:11 +0000 (15:30 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 13 Jul 2023 08:06:47 +0000 (17:06 +0900)
src/network/networkd-json.c

index c32642205ef1260b18af9da44741dde7184018df..b9e119ceeb847807b19e25e21c16d1cebcca723a 100644 (file)
 #include "user-util.h"
 #include "wifi-util.h"
 
+static int json_append_one(JsonVariant **v, const char *name, JsonVariant *w) {
+        assert(v);
+        assert(name);
+
+        return json_append(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT_NON_NULL(name, w)));
+}
+
 static int address_build_json(Address *address, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         _cleanup_free_ char *scope = NULL, *flags = NULL, *state = NULL;
         int r;
 
@@ -41,7 +47,7 @@ static int address_build_json(Address *address, JsonVariant **ret) {
         if (r < 0)
                 return r;
 
-        r = json_build(&v, JSON_BUILD_OBJECT(
+        return json_build(ret, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_INTEGER("Family", address->family),
                                 JSON_BUILD_PAIR_IN_ADDR("Address", &address->in_addr, address->family),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Peer", &address->in_addr_peer, address->family),
@@ -59,44 +65,31 @@ static int address_build_json(Address *address, JsonVariant **ret) {
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(address->source)),
                                 JSON_BUILD_PAIR_STRING("ConfigState", state),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &address->provider, address->family)));
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(v);
-        return 0;
 }
 
-static int addresses_build_json(Set *addresses, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+static int addresses_append_json(Set *addresses, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         Address *address;
-        size_t n = 0;
         int r;
 
-        assert(ret);
-
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        assert(v);
 
-        if (set_isempty(addresses)) {
-                *ret = NULL;
-                return 0;
-        }
+        SET_FOREACH(address, addresses) {
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-        elements = new(JsonVariant*, set_size(addresses));
-        if (!elements)
-                return -ENOMEM;
+                r = address_build_json(address, &e);
+                if (r < 0)
+                        return r;
 
-        SET_FOREACH(address, addresses) {
-                r = address_build_json(address, elements + n);
+                r = json_variant_append_array(&array, e);
                 if (r < 0)
                         return r;
-                n++;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("Addresses", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "Addresses", array);
 }
 
 static int neighbor_build_json(Neighbor *n, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         _cleanup_free_ char *state = NULL;
         int r;
 
@@ -107,83 +100,64 @@ static int neighbor_build_json(Neighbor *n, JsonVariant **ret) {
         if (r < 0)
                 return r;
 
-        r = json_build(&v, JSON_BUILD_OBJECT(
+        return json_build(ret, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_INTEGER("Family", n->family),
                                 JSON_BUILD_PAIR_IN_ADDR("Destination", &n->in_addr, n->family),
                                 JSON_BUILD_PAIR_HW_ADDR("LinkLayerAddress", &n->ll_addr),
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(n->source)),
                                 JSON_BUILD_PAIR_STRING("ConfigState", state)));
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(v);
-        return 0;
 }
 
-static int neighbors_build_json(Set *neighbors, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+static int neighbors_append_json(Set *neighbors, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         Neighbor *neighbor;
-        size_t n = 0;
         int r;
 
-        assert(ret);
-
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        assert(v);
 
-        if (set_isempty(neighbors)) {
-                *ret = NULL;
-                return 0;
-        }
+        SET_FOREACH(neighbor, neighbors) {
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-        elements = new(JsonVariant*, set_size(neighbors));
-        if (!elements)
-                return -ENOMEM;
+                r = neighbor_build_json(neighbor, &e);
+                if (r < 0)
+                        return r;
 
-        SET_FOREACH(neighbor, neighbors) {
-                r = neighbor_build_json(neighbor, elements + n);
+                r = json_variant_append_array(&array, e);
                 if (r < 0)
                         return r;
-                n++;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("Neighbors", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "Neighbors", array);
 }
 
 static int nexthop_group_build_json(NextHop *nexthop, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         struct nexthop_grp *g;
-        size_t n = 0;
         int r;
 
         assert(nexthop);
         assert(ret);
 
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
-
-        if (hashmap_isempty(nexthop->group)) {
-                *ret = NULL;
-                return 0;
-        }
-
-        elements = new(JsonVariant*, hashmap_size(nexthop->group));
-        if (!elements)
-                return -ENOMEM;
-
         HASHMAP_FOREACH(g, nexthop->group) {
-                r = json_build(elements + n, JSON_BUILD_OBJECT(
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
+
+                r = json_build(&e, JSON_BUILD_OBJECT(
                                         JSON_BUILD_PAIR_UNSIGNED("ID", g->id),
                                         JSON_BUILD_PAIR_UNSIGNED("Weight", g->weight+1)));
                 if (r < 0)
                         return r;
 
-                n++;
+                r = json_variant_append_array(&array, e);
+                if (r < 0)
+                        return r;
         }
 
-        return json_variant_new_array(ret, elements, n);
+        *ret = TAKE_PTR(array);
+        return 0;
 }
 
 static int nexthop_build_json(NextHop *n, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *group = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *group = NULL;
         _cleanup_free_ char *flags = NULL, *protocol = NULL, *state = NULL;
         int r;
 
@@ -206,7 +180,7 @@ static int nexthop_build_json(NextHop *n, JsonVariant **ret) {
         if (r < 0)
                 return r;
 
-        r = json_build(&v, JSON_BUILD_OBJECT(
+        return json_build(ret, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_UNSIGNED("ID", n->id),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &n->gw, n->family),
                                 JSON_BUILD_PAIR_UNSIGNED("Flags", n->flags),
@@ -217,44 +191,31 @@ static int nexthop_build_json(NextHop *n, JsonVariant **ret) {
                                 JSON_BUILD_PAIR_VARIANT_NON_NULL("Group", group),
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(n->source)),
                                 JSON_BUILD_PAIR_STRING("ConfigState", state)));
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(v);
-        return 0;
 }
 
-static int nexthops_build_json(Set *nexthops, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+static int nexthops_append_json(Set *nexthops, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         NextHop *nexthop;
-        size_t n = 0;
         int r;
 
-        assert(ret);
-
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        assert(v);
 
-        if (set_isempty(nexthops)) {
-                *ret = NULL;
-                return 0;
-        }
+        SET_FOREACH(nexthop, nexthops) {
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-        elements = new(JsonVariant*, set_size(nexthops));
-        if (!elements)
-                return -ENOMEM;
+                r = nexthop_build_json(nexthop, &e);
+                if (r < 0)
+                        return r;
 
-        SET_FOREACH(nexthop, nexthops) {
-                r = nexthop_build_json(nexthop, elements + n);
+                r = json_variant_append_array(&array, e);
                 if (r < 0)
                         return r;
-                n++;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("NextHops", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "NextHops", array);
 }
 
 static int route_build_json(Route *route, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         _cleanup_free_ char *scope = NULL, *protocol = NULL, *table = NULL, *flags = NULL, *state = NULL;
         Manager *manager;
         int r;
@@ -286,7 +247,7 @@ static int route_build_json(Route *route, JsonVariant **ret) {
         if (r < 0)
                 return r;
 
-        r = json_build(&v, JSON_BUILD_OBJECT(
+        return json_build(ret, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_INTEGER("Family", route->family),
                                 JSON_BUILD_PAIR_IN_ADDR("Destination", &route->dst, route->family),
                                 JSON_BUILD_PAIR_UNSIGNED("DestinationPrefixLength", route->dst_prefixlen),
@@ -312,44 +273,31 @@ static int route_build_json(Route *route, JsonVariant **ret) {
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(route->source)),
                                 JSON_BUILD_PAIR_STRING("ConfigState", state),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &route->provider, route->family)));
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(v);
-        return 0;
 }
 
-static int routes_build_json(Set *routes, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+static int routes_append_json(Set *routes, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         Route *route;
-        size_t n = 0;
         int r;
 
-        assert(ret);
+        assert(v);
 
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
-
-        if (set_isempty(routes)) {
-                *ret = NULL;
-                return 0;
-        }
+        SET_FOREACH(route, routes) {
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-        elements = new(JsonVariant*, set_size(routes));
-        if (!elements)
-                return -ENOMEM;
+                r = route_build_json(route, &e);
+                if (r < 0)
+                        return r;
 
-        SET_FOREACH(route, routes) {
-                r = route_build_json(route, elements + n);
+                r = json_variant_append_array(&array, e);
                 if (r < 0)
                         return r;
-                n++;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("Routes", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "Routes", array);
 }
 
 static int routing_policy_rule_build_json(RoutingPolicyRule *rule, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         _cleanup_free_ char *table = NULL, *protocol = NULL, *state = NULL;
         int r;
 
@@ -369,7 +317,7 @@ static int routing_policy_rule_build_json(RoutingPolicyRule *rule, JsonVariant *
         if (r < 0)
                 return r;
 
-        r = json_build(&v, JSON_BUILD_OBJECT(
+        return json_build(ret, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_INTEGER("Family", rule->family),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("FromPrefix", &rule->from, rule->family),
                                 JSON_BUILD_PAIR_CONDITION(in_addr_is_set(rule->family, &rule->from),
@@ -404,51 +352,37 @@ static int routing_policy_rule_build_json(RoutingPolicyRule *rule, JsonVariant *
                                 JSON_BUILD_PAIR_STRING_NON_EMPTY("OutgoingInterface", rule->oif),
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(rule->source)),
                                 JSON_BUILD_PAIR_STRING("ConfigState", state)));
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(v);
-        return 0;
 }
 
-static int routing_policy_rules_build_json(Set *rules, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+static int routing_policy_rules_append_json(Set *rules, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         RoutingPolicyRule *rule;
-        size_t n = 0;
         int r;
 
-        assert(ret);
-
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        assert(v);
 
-        if (set_isempty(rules)) {
-                *ret = NULL;
-                return 0;
-        }
+        SET_FOREACH(rule, rules) {
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-        elements = new(JsonVariant*, set_size(rules));
-        if (!elements)
-                return -ENOMEM;
+                r = routing_policy_rule_build_json(rule, &e);
+                if (r < 0)
+                        return r;
 
-        SET_FOREACH(rule, rules) {
-                r = routing_policy_rule_build_json(rule, elements + n);
+                r = json_variant_append_array(&array, e);
                 if (r < 0)
                         return r;
-                n++;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("RoutingPolicyRules", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "RoutingPolicyRules", array);
 }
 
-static int network_build_json(Network *network, JsonVariant **ret) {
-        assert(ret);
+static int network_append_json(Network *network, JsonVariant **v) {
+        assert(v);
 
-        if (!network) {
-                *ret = NULL;
+        if (!network)
                 return 0;
-        }
 
-        return json_build(ret, JSON_BUILD_OBJECT(
+        return json_append(v, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_STRING("NetworkFile", network->filename),
                                 JSON_BUILD_PAIR_STRV("NetworkFileDropins", network->dropins),
                                 JSON_BUILD_PAIR_BOOLEAN("RequiredForOnline", network->required_for_online),
@@ -461,17 +395,15 @@ static int network_build_json(Network *network, JsonVariant **ret) {
                                                        activation_policy_to_string(network->activation_policy))));
 }
 
-static int device_build_json(sd_device *device, JsonVariant **ret) {
+static int device_append_json(sd_device *device, JsonVariant **v) {
         _cleanup_strv_free_ char **link_dropins = NULL;
         const char *link = NULL, *path = NULL, *vendor = NULL, *model = NULL, *joined;
         int r;
 
-        assert(ret);
+        assert(v);
 
-        if (!device) {
-                *ret = NULL;
+        if (!device)
                 return 0;
-        }
 
         (void) sd_device_get_property_value(device, "ID_NET_LINK_FILE", &link);
 
@@ -489,7 +421,7 @@ static int device_build_json(sd_device *device, JsonVariant **ret) {
         if (sd_device_get_property_value(device, "ID_MODEL_FROM_DATABASE", &model) < 0)
                 (void) sd_device_get_property_value(device, "ID_MODEL", &model);
 
-        return json_build(ret, JSON_BUILD_OBJECT(
+        return json_append(v, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_STRING_NON_EMPTY("LinkFile", link),
                                 JSON_BUILD_PAIR_STRV_NON_EMPTY("LinkFileDropins", link_dropins),
                                 JSON_BUILD_PAIR_STRING_NON_EMPTY("Path", path),
@@ -497,18 +429,16 @@ static int device_build_json(sd_device *device, JsonVariant **ret) {
                                 JSON_BUILD_PAIR_STRING_NON_EMPTY("Model", model)));
 }
 
-static int dns_build_json_one(Link *link, const struct in_addr_full *a, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **ret) {
+static int dns_append_json_one(Link *link, const struct in_addr_full *a, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) {
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         int r;
 
         assert(link);
         assert(a);
-        assert(ret);
+        assert(array);
 
-        if (a->ifindex != 0 && a->ifindex != link->ifindex) {
-                *ret = NULL;
+        if (a->ifindex != 0 && a->ifindex != link->ifindex)
                 return 0;
-        }
 
         r = json_build(&v, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_INTEGER("Family", a->family),
@@ -521,46 +451,30 @@ static int dns_build_json_one(Link *link, const struct in_addr_full *a, NetworkC
         if (r < 0)
                 return r;
 
-        *ret = TAKE_PTR(v);
-        return 1;
+        return json_variant_append_array(array, v);
 }
 
-static int dns_build_json(Link *link, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
-        size_t n = 0;
+static int dns_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         int r;
 
         assert(link);
-        assert(ret);
+        assert(v);
 
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
-
-        if (!link->network) {
-                *ret = NULL;
+        if (!link->network)
                 return 0;
-        }
 
-        if (link->n_dns != UINT_MAX) {
+        if (link->n_dns != UINT_MAX)
                 for (unsigned i = 0; i < link->n_dns; i++) {
-                        if (!GREEDY_REALLOC(elements, n + 1))
-                                return -ENOMEM;
-
-                        r = dns_build_json_one(link, link->dns[i], NETWORK_CONFIG_SOURCE_RUNTIME, NULL, elements + n);
+                        r = dns_append_json_one(link, link->dns[i], NETWORK_CONFIG_SOURCE_RUNTIME, NULL, &array);
                         if (r < 0)
                                 return r;
-                        if (r > 0)
-                                n++;
                 }
-        else {
+        else {
                 for (unsigned i = 0; i < link->network->n_dns; i++) {
-                        if (!GREEDY_REALLOC(elements, n + 1))
-                                return -ENOMEM;
-
-                        r = dns_build_json_one(link, link->network->dns[i], NETWORK_CONFIG_SOURCE_STATIC, NULL, elements + n);
+                        r = dns_append_json_one(link, link->network->dns[i], NETWORK_CONFIG_SOURCE_STATIC, NULL, &array);
                         if (r < 0)
                                 return r;
-                        if (r > 0)
-                                n++;
                 }
 
                 if (link->dhcp_lease && link->network->dhcp_use_dns) {
@@ -574,18 +488,13 @@ static int dns_build_json(Link *link, JsonVariant **ret) {
 
                         n_dns = sd_dhcp_lease_get_dns(link->dhcp_lease, &dns);
                         for (int i = 0; i < n_dns; i++) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = dns_build_json_one(link,
-                                                       &(struct in_addr_full) { .family = AF_INET, .address.in = dns[i], },
-                                                       NETWORK_CONFIG_SOURCE_DHCP4,
-                                                       &s,
-                                                       elements + n);
+                                r = dns_append_json_one(link,
+                                                        &(struct in_addr_full) { .family = AF_INET, .address.in = dns[i], },
+                                                        NETWORK_CONFIG_SOURCE_DHCP4,
+                                                        &s,
+                                                        &array);
                                 if (r < 0)
                                         return r;
-                                if (r > 0)
-                                        n++;
                         }
                 }
 
@@ -600,18 +509,13 @@ static int dns_build_json(Link *link, JsonVariant **ret) {
 
                         n_dns = sd_dhcp6_lease_get_dns(link->dhcp6_lease, &dns);
                         for (int i = 0; i < n_dns; i++) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = dns_build_json_one(link,
-                                                       &(struct in_addr_full) { .family = AF_INET6, .address.in6 = dns[i], },
-                                                       NETWORK_CONFIG_SOURCE_DHCP6,
-                                                       &s,
-                                                       elements + n);
+                                r = dns_append_json_one(link,
+                                                        &(struct in_addr_full) { .family = AF_INET6, .address.in6 = dns[i], },
+                                                        NETWORK_CONFIG_SOURCE_DHCP6,
+                                                        &s,
+                                                        &array);
                                 if (r < 0)
                                         return r;
-                                if (r > 0)
-                                        n++;
                         }
                 }
 
@@ -619,90 +523,83 @@ static int dns_build_json(Link *link, JsonVariant **ret) {
                         NDiscRDNSS *a;
 
                         SET_FOREACH(a, link->ndisc_rdnss) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = dns_build_json_one(link,
-                                                       &(struct in_addr_full) { .family = AF_INET6, .address.in6 = a->address, },
-                                                       NETWORK_CONFIG_SOURCE_NDISC,
-                                                       &(union in_addr_union) { .in6 = a->router },
-                                                       elements + n);
+                                r = dns_append_json_one(link,
+                                                        &(struct in_addr_full) { .family = AF_INET6, .address.in6 = a->address, },
+                                                        NETWORK_CONFIG_SOURCE_NDISC,
+                                                        &(union in_addr_union) { .in6 = a->router },
+                                                        &array);
                                 if (r < 0)
                                         return r;
-                                if (r > 0)
-                                        n++;
                         }
                 }
         }
 
-        if (n == 0) {
-                *ret = NULL;
-                return 0;
-        }
-
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("DNS", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "DNS", array);
 }
 
-static int server_build_json_one_addr(int family, const union in_addr_union *a, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **ret) {
+static int server_append_json_one_addr(int family, const union in_addr_union *a, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        int r;
+
         assert(IN_SET(family, AF_INET, AF_INET6));
         assert(a);
-        assert(ret);
+        assert(array);
 
-        return json_build(ret, JSON_BUILD_OBJECT(
+        r = json_build(&v, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_INTEGER("Family", family),
                                 JSON_BUILD_PAIR_IN_ADDR("Address", a, family),
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family)));
+        if (r < 0)
+                return r;
+
+        return json_variant_append_array(array, v);
 }
 
-static int server_build_json_one_fqdn(int family, const char *fqdn, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **ret) {
+static int server_append_json_one_fqdn(int family, const char *fqdn, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        int r;
+
         assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6));
         assert(fqdn);
-        assert(ret);
+        assert(array);
 
-        return json_build(ret, JSON_BUILD_OBJECT(
+        r = json_build(&v, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_STRING("Server", fqdn),
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family)));
+        if (r < 0)
+                return r;
+
+        return json_variant_append_array(array, v);
 }
 
-static int server_build_json_one_string(const char *str, NetworkConfigSource s, JsonVariant **ret) {
+static int server_append_json_one_string(const char *str, NetworkConfigSource s, JsonVariant **array) {
         union in_addr_union a;
         int family;
 
         assert(str);
-        assert(ret);
 
         if (in_addr_from_string_auto(str, &family, &a) >= 0)
-                return server_build_json_one_addr(family, &a, s, NULL, ret);
+                return server_append_json_one_addr(family, &a, s, NULL, array);
 
-        return server_build_json_one_fqdn(AF_UNSPEC, str, s, NULL, ret);
+        return server_append_json_one_fqdn(AF_UNSPEC, str, s, NULL, array);
 }
 
-static int ntp_build_json(Link *link, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
-        size_t n = 0;
+static int ntp_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         int r;
 
         assert(link);
-        assert(ret);
+        assert(v);
 
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
-
-        if (!link->network) {
-                *ret = NULL;
+        if (!link->network)
                 return 0;
-        }
 
         STRV_FOREACH(p, link->ntp ?: link->network->ntp) {
-                if (!GREEDY_REALLOC(elements, n + 1))
-                        return -ENOMEM;
-
-                r = server_build_json_one_string(*p, NETWORK_CONFIG_SOURCE_RUNTIME, elements + n);
+                r = server_append_json_one_string(*p, NETWORK_CONFIG_SOURCE_RUNTIME, &array);
                 if (r < 0)
                         return r;
-
-                n++;
         }
 
         if (!link->ntp) {
@@ -717,18 +614,13 @@ static int ntp_build_json(Link *link, JsonVariant **ret) {
 
                         n_ntp = sd_dhcp_lease_get_ntp(link->dhcp_lease, &ntp);
                         for (int i = 0; i < n_ntp; i++) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = server_build_json_one_addr(AF_INET,
-                                                               &(union in_addr_union) { .in = ntp[i], },
-                                                               NETWORK_CONFIG_SOURCE_DHCP4,
-                                                               &s,
-                                                               elements + n);
+                                r = server_append_json_one_addr(AF_INET,
+                                                                &(union in_addr_union) { .in = ntp[i], },
+                                                                NETWORK_CONFIG_SOURCE_DHCP4,
+                                                                &s,
+                                                                &array);
                                 if (r < 0)
                                         return r;
-
-                                n++;
                         }
                 }
 
@@ -744,157 +636,107 @@ static int ntp_build_json(Link *link, JsonVariant **ret) {
 
                         n_ntp = sd_dhcp6_lease_get_ntp_addrs(link->dhcp6_lease, &ntp_addr);
                         for (int i = 0; i < n_ntp; i++) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = server_build_json_one_addr(AF_INET6,
-                                                               &(union in_addr_union) { .in6 = ntp_addr[i], },
-                                                               NETWORK_CONFIG_SOURCE_DHCP6,
-                                                               &s,
-                                                               elements + n);
+                                r = server_append_json_one_addr(AF_INET6,
+                                                                &(union in_addr_union) { .in6 = ntp_addr[i], },
+                                                                NETWORK_CONFIG_SOURCE_DHCP6,
+                                                                &s,
+                                                                &array);
                                 if (r < 0)
                                         return r;
-
-                                n++;
                         }
 
                         n_ntp = sd_dhcp6_lease_get_ntp_fqdn(link->dhcp6_lease, &ntp_fqdn);
                         for (int i = 0; i < n_ntp; i++) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = server_build_json_one_fqdn(AF_INET6,
-                                                               ntp_fqdn[i],
-                                                               NETWORK_CONFIG_SOURCE_DHCP6,
-                                                               &s,
-                                                               elements + n);
+                                r = server_append_json_one_fqdn(AF_INET6,
+                                                                ntp_fqdn[i],
+                                                                NETWORK_CONFIG_SOURCE_DHCP6,
+                                                                &s,
+                                                                &array);
                                 if (r < 0)
                                         return r;
-
-                                n++;
                         }
                 }
         }
 
-        if (n == 0) {
-                *ret = NULL;
-                return 0;
-        }
-
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("NTP", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "NTP", array);
 }
 
-static int sip_build_json(Link *link, JsonVariant **ret) {
+static int sip_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         const struct in_addr *sip;
-        JsonVariant **elements = NULL;
         union in_addr_union s;
-        size_t n = 0;
         int n_sip, r;
 
         assert(link);
-        assert(ret);
+        assert(v);
 
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
-
-        if (!link->network || !link->network->dhcp_use_sip || !link->dhcp_lease) {
-                *ret = NULL;
+        if (!link->network || !link->network->dhcp_use_sip || !link->dhcp_lease)
                 return 0;
-        }
 
         n_sip = sd_dhcp_lease_get_sip(link->dhcp_lease, &sip);
-        if (n_sip <= 0) {
-                *ret = NULL;
+        if (n_sip <= 0)
                 return 0;
-        }
 
         r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in);
         if (r < 0)
                 return r;
 
-        elements = new(JsonVariant*, n_sip);
-        if (!elements)
-                return -ENOMEM;
-
         for (int i = 0; i < n_sip; i++) {
-                r = server_build_json_one_addr(AF_INET,
-                                               &(union in_addr_union) { .in = sip[i], },
-                                               NETWORK_CONFIG_SOURCE_DHCP4,
-                                               &s,
-                                               elements + n);
+                r = server_append_json_one_addr(AF_INET,
+                                                &(union in_addr_union) { .in = sip[i], },
+                                                NETWORK_CONFIG_SOURCE_DHCP4,
+                                                &s,
+                                                &array);
                 if (r < 0)
                         return r;
-                if (r > 0)
-                        n++;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("SIP", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "SIP", array);
 }
 
-static int captive_portal_build_json(Link *link, JsonVariant **ret) {
-        const char *captive_portal;
+static int domain_append_json(int family, const char *domain, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         int r;
 
-        assert(link);
-        assert(ret);
-
-        r = link_get_captive_portal(link, &captive_portal);
-        if (r < 0)
-                return r;
-
-        if (!captive_portal) {
-                *ret = NULL;
-                return 0;
-        }
-
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("CaptivePortal", captive_portal)));
-}
-
-static int domain_build_json(int family, const char *domain, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **ret) {
         assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6));
         assert(domain);
-        assert(ret);
+        assert(array);
 
-        return json_build(ret, JSON_BUILD_OBJECT(
+        r = json_build(&v, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_STRING("Domain", domain),
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
                                 JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family)));
+        if (r < 0)
+                return r;
+
+        return json_variant_append_array(array, v);
 }
 
-static int domains_build_json(Link *link, bool is_route, JsonVariant **ret) {
+static int domains_append_json(Link *link, bool is_route, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         OrderedSet *link_domains, *network_domains;
-        JsonVariant **elements = NULL;
         DHCPUseDomains use_domains;
         union in_addr_union s;
         char **domains;
         const char *domain;
-        size_t n = 0;
         int r;
 
         assert(link);
-        assert(ret);
-
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        assert(v);
 
-        if (!link->network) {
-                *ret = NULL;
+        if (!link->network)
                 return 0;
-        }
 
         link_domains = is_route ? link->route_domains : link->search_domains;
         network_domains = is_route ? link->network->route_domains : link->network->search_domains;
         use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES;
 
         ORDERED_SET_FOREACH(domain, link_domains ?: network_domains) {
-                if (!GREEDY_REALLOC(elements, n + 1))
-                        return -ENOMEM;
-
-                r = domain_build_json(AF_UNSPEC, domain,
-                                      link_domains ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC,
-                                      NULL, elements + n);
+                r = domain_append_json(AF_UNSPEC, domain,
+                                       link_domains ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC,
+                                       NULL, &array);
                 if (r < 0)
                         return r;
-
-                n++;
         }
 
         if (!link_domains) {
@@ -905,28 +747,17 @@ static int domains_build_json(Link *link, bool is_route, JsonVariant **ret) {
                                 return r;
 
                         if (sd_dhcp_lease_get_domainname(link->dhcp_lease, &domain) >= 0) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = domain_build_json(AF_INET, domain, NETWORK_CONFIG_SOURCE_DHCP4, &s, elements + n);
+                                r = domain_append_json(AF_INET, domain, NETWORK_CONFIG_SOURCE_DHCP4, &s, &array);
                                 if (r < 0)
                                         return r;
-
-                                n++;
                         }
 
-                        if (sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains) >= 0) {
+                        if (sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains) >= 0)
                                 STRV_FOREACH(p, domains) {
-                                        if (!GREEDY_REALLOC(elements, n + 1))
-                                                return -ENOMEM;
-
-                                        r = domain_build_json(AF_INET, *p, NETWORK_CONFIG_SOURCE_DHCP4, &s, elements + n);
+                                        r = domain_append_json(AF_INET, *p, NETWORK_CONFIG_SOURCE_DHCP4, &s, &array);
                                         if (r < 0)
                                                 return r;
-
-                                        n++;
                                 }
-                        }
                 }
 
                 if (link->dhcp6_lease &&
@@ -935,197 +766,176 @@ static int domains_build_json(Link *link, bool is_route, JsonVariant **ret) {
                         if (r < 0)
                                 return r;
 
-                        if (sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains) >= 0) {
+                        if (sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains) >= 0)
                                 STRV_FOREACH(p, domains) {
-                                        if (!GREEDY_REALLOC(elements, n + 1))
-                                                return -ENOMEM;
-
-                                        r = domain_build_json(AF_INET6, *p, NETWORK_CONFIG_SOURCE_DHCP6, &s, elements + n);
+                                        r = domain_append_json(AF_INET6, *p, NETWORK_CONFIG_SOURCE_DHCP6, &s, &array);
                                         if (r < 0)
                                                 return r;
-
-                                        n++;
                                 }
-                        }
                 }
 
                 if (link->network->ipv6_accept_ra_use_domains == use_domains) {
                         NDiscDNSSL *a;
 
                         SET_FOREACH(a, link->ndisc_dnssl) {
-                                if (!GREEDY_REALLOC(elements, n + 1))
-                                        return -ENOMEM;
-
-                                r = domain_build_json(AF_INET6, NDISC_DNSSL_DOMAIN(a), NETWORK_CONFIG_SOURCE_NDISC,
-                                                      &(union in_addr_union) { .in6 = a->router },
-                                                      elements + n);
+                                r = domain_append_json(AF_INET6, NDISC_DNSSL_DOMAIN(a), NETWORK_CONFIG_SOURCE_NDISC,
+                                                       &(union in_addr_union) { .in6 = a->router },
+                                                       &array);
                                 if (r < 0)
                                         return r;
-
-                                n++;
                         }
                 }
         }
 
-        if (n == 0) {
-                *ret = NULL;
-                return 0;
-        }
-
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR(is_route ? "RouteDomains" : "SearchDomains",
-                                                                 JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, is_route ? "RouteDomains" : "SearchDomains", array);
 }
 
-static int nta_build_json(const char *nta, NetworkConfigSource s, JsonVariant **ret) {
+static int nta_append_json(const char *nta, NetworkConfigSource s, JsonVariant **array) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        int r;
+
         assert(nta);
-        assert(ret);
+        assert(array);
 
-        return json_build(ret, JSON_BUILD_OBJECT(
+        r = json_build(&v, JSON_BUILD_OBJECT(
                                 JSON_BUILD_PAIR_STRING("DNSSECNegativeTrustAnchor", nta),
                                 JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s))));
+        if (r < 0)
+                return r;
+
+        return json_variant_append_array(array, v);
 }
 
-static int ntas_build_json(Link *link, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+static int ntas_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         const char *nta;
-        size_t n = 0;
         int r;
 
         assert(link);
-        assert(ret);
-
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        assert(v);
 
-        if (!link->network) {
-                *ret = NULL;
+        if (!link->network)
                 return 0;
-        }
 
         SET_FOREACH(nta, link->dnssec_negative_trust_anchors ?: link->network->dnssec_negative_trust_anchors) {
-                if (!GREEDY_REALLOC(elements, n + 1))
-                        return -ENOMEM;
-
-                r = nta_build_json(nta,
+                r = nta_append_json(nta,
                                    link->dnssec_negative_trust_anchors ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC,
-                                   elements + n);
+                                   &array);
                 if (r < 0)
                         return r;
-
-                n++;
-        }
-
-        if (n == 0) {
-                *ret = NULL;
-                return 0;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("DNSSECNegativeTrustAnchors",
-                                                                 JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "DNSSECNegativeTrustAnchors", array);
 }
 
-static int dns_misc_build_json(Link *link, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
+static int dns_misc_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         ResolveSupport resolve_support;
         NetworkConfigSource source;
         DnsOverTlsMode mode;
-        size_t n = 0;
         int t, r;
 
         assert(link);
-        assert(ret);
-
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        assert(v);
 
-        if (!link->network) {
-                *ret = NULL;
+        if (!link->network)
                 return 0;
-        }
 
         resolve_support = link->llmnr >= 0 ? link->llmnr : link->network->llmnr;
         if (resolve_support >= 0) {
-                source = link->llmnr >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-                if (!GREEDY_REALLOC(elements, n + 1))
-                        return -ENOMEM;
+                source = link->llmnr >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
 
-                r = json_build(elements + n, JSON_BUILD_OBJECT(
+                r = json_build(&e, JSON_BUILD_OBJECT(
                                         JSON_BUILD_PAIR_STRING("LLMNR", resolve_support_to_string(resolve_support)),
                                         JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source))));
                 if (r < 0)
                         return r;
 
-                n++;
+                r = json_variant_append_array(&array, e);
+                if (r < 0)
+                        return r;
         }
 
         resolve_support = link->mdns >= 0 ? link->mdns : link->network->mdns;
         if (resolve_support >= 0) {
-                source = link->mdns >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-                if (!GREEDY_REALLOC(elements, n + 1))
-                        return -ENOMEM;
+                source = link->mdns >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
 
-                r = json_build(elements + n, JSON_BUILD_OBJECT(
+                r = json_build(&e, JSON_BUILD_OBJECT(
                                         JSON_BUILD_PAIR_STRING("MDNS", resolve_support_to_string(resolve_support)),
                                         JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source))));
                 if (r < 0)
                         return r;
 
-                n++;
+                r = json_variant_append_array(&array, e);
+                if (r < 0)
+                        return r;
         }
 
         t = link->dns_default_route >= 0 ? link->dns_default_route : link->network->dns_default_route;
         if (t >= 0) {
-                source = link->dns_default_route >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-                if (!GREEDY_REALLOC(elements, n + 1))
-                        return -ENOMEM;
+                source = link->dns_default_route >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
 
-                r = json_build(elements + n, JSON_BUILD_OBJECT(
+                r = json_build(&e, JSON_BUILD_OBJECT(
                                         JSON_BUILD_PAIR_BOOLEAN("DNSDefaultRoute", t),
                                         JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source))));
                 if (r < 0)
                         return r;
 
-                n++;
+                r = json_variant_append_array(&array, e);
+                if (r < 0)
+                        return r;
         }
 
         mode = link->dns_over_tls_mode >= 0 ? link->dns_over_tls_mode : link->network->dns_over_tls_mode;
         if (mode >= 0) {
-                source = link->dns_over_tls_mode >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-                if (!GREEDY_REALLOC(elements, n + 1))
-                        return -ENOMEM;
+                source = link->dns_over_tls_mode >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC;
 
-                r = json_build(elements + n, JSON_BUILD_OBJECT(
+                r = json_build(&e, JSON_BUILD_OBJECT(
                                         JSON_BUILD_PAIR_STRING("DNSOverTLS", dns_over_tls_mode_to_string(mode)),
                                         JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source))));
                 if (r < 0)
                         return r;
 
-                n++;
+                r = json_variant_append_array(&array, e);
+                if (r < 0)
+                        return r;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("DNSSettings",
-                                                                 JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "DNSSettings", array);
 }
 
-static int dhcp_server_offered_leases_build_json(Link *link, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *elements = NULL;
+static int captive_portal_append_json(Link *link, JsonVariant **v) {
+        const char *captive_portal;
         int r;
-        DHCPLease *lease;
 
         assert(link);
-        assert(ret);
+        assert(v);
 
-        if (!link->dhcp_server) {
-                *ret = NULL;
-                return 0;
-        }
+        r = link_get_captive_portal(link, &captive_portal);
+        if (r <= 0)
+                return r;
+
+        return json_append(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("CaptivePortal", captive_portal)));
+}
 
-        if (hashmap_isempty(link->dhcp_server->bound_leases_by_client_id)) {
-                *ret = NULL;
+static int dhcp_server_offered_leases_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+        DHCPLease *lease;
+        int r;
+
+        assert(link);
+        assert(v);
+
+        if (!link->dhcp_server)
                 return 0;
-        }
 
         HASHMAP_FOREACH(lease, link->dhcp_server->bound_leases_by_client_id) {
                 _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
@@ -1144,31 +954,24 @@ static int dhcp_server_offered_leases_build_json(Link *link, JsonVariant **ret)
                 if (r < 0)
                         return r;
 
-                r = json_variant_append_array(&elements, e);
+                r = json_variant_append_array(&array, e);
                 if (r < 0)
                         return r;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT("Leases", elements)));
+        return json_append_one(v, "Leases", array);
 }
 
-static int dhcp_server_static_leases_build_json(Link *link, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *elements = NULL;
-        int r;
+static int dhcp_server_static_leases_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
         DHCPLease *lease;
+        int r;
 
         assert(link);
-        assert(ret);
+        assert(v);
 
-        if (!link->dhcp_server) {
-                *ret = NULL;
+        if (!link->dhcp_server)
                 return 0;
-        }
-
-        if (hashmap_isempty(link->dhcp_server->static_leases_by_client_id)) {
-                *ret = NULL;
-                return 0;
-        }
 
         HASHMAP_FOREACH(lease, link->dhcp_server->static_leases_by_client_id) {
                 _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
@@ -1184,56 +987,44 @@ static int dhcp_server_static_leases_build_json(Link *link, JsonVariant **ret) {
                 if (r < 0)
                         return r;
 
-                r = json_variant_append_array(&elements, e);
+                r = json_variant_append_array(&array, e);
                 if (r < 0)
                         return r;
         }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT("StaticLeases", elements)));
+        return json_append_one(v, "StaticLeases", array);
 }
 
-static int dhcp_server_build_json(Link *link, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
+static int dhcp_server_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
         int r;
 
         assert(link);
-        assert(ret);
+        assert(v);
 
-        if (!link->dhcp_server) {
-                *ret = NULL;
+        if (!link->dhcp_server)
                 return 0;
-        }
 
-        r = json_build(&v,
+        r = json_build(&w,
                        JSON_BUILD_OBJECT(
                                        JSON_BUILD_PAIR_UNSIGNED("PoolOffset", link->dhcp_server->pool_offset),
                                        JSON_BUILD_PAIR_UNSIGNED("PoolSize", link->dhcp_server->pool_size)));
         if (r < 0)
                 return r;
 
-        r = dhcp_server_offered_leases_build_json(link, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
+        r = dhcp_server_offered_leases_append_json(link, &w);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = dhcp_server_static_leases_build_json(link, &w);
+        r = dhcp_server_static_leases_append_json(link, &w);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT("DHCPServer", v)));
+        return json_append_one(v, "DHCPServer", w);
 }
 
 int link_build_json(Link *link, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         _cleanup_free_ char *type = NULL, *flags = NULL;
         int r;
 
@@ -1287,151 +1078,63 @@ int link_build_json(Link *link, JsonVariant **ret) {
         if (r < 0)
                 return r;
 
-        r = network_build_json(link->network, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = device_build_json(link->dev, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = dns_build_json(link, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = ntp_build_json(link, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = sip_build_json(link, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = captive_portal_build_json(link, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = domains_build_json(link, /* is_route = */ false, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = domains_build_json(link, /* is_route = */ true, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
+        r = network_append_json(link->network, &v);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = ntas_build_json(link, &w);
+        r = device_append_json(link->dev, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = dns_append_json(link, &v);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = dns_misc_build_json(link, &w);
+        r = ntp_append_json(link, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = sip_append_json(link, &v);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = addresses_build_json(link->addresses, &w);
+        r = domains_append_json(link, /* is_route = */ false, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = domains_append_json(link, /* is_route = */ true, &v);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = neighbors_build_json(link->neighbors, &w);
+        r = ntas_append_json(link, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = dns_misc_append_json(link, &v);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = nexthops_build_json(link->nexthops, &w);
+        r = captive_portal_append_json(link, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = addresses_append_json(link->addresses, &v);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = routes_build_json(link->routes, &w);
+        r = neighbors_append_json(link->neighbors, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = nexthops_append_json(link->nexthops, &v);
         if (r < 0)
                 return r;
 
-        w = json_variant_unref(w);
-
-        r = dhcp_server_build_json(link, &w);
+        r = routes_append_json(link->routes, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = dhcp_server_append_json(link, &v);
         if (r < 0)
                 return r;
 
@@ -1439,81 +1142,54 @@ int link_build_json(Link *link, JsonVariant **ret) {
         return 0;
 }
 
-static int link_json_compare(JsonVariant * const *a, JsonVariant * const *b) {
-        int64_t index_a, index_b;
-
-        assert(a && *a);
-        assert(b && *b);
-
-        index_a = json_variant_integer(json_variant_by_key(*a, "Index"));
-        index_b = json_variant_integer(json_variant_by_key(*b, "Index"));
-
-        return CMP(index_a, index_b);
-}
-
-static int links_build_json(Manager *manager, JsonVariant **ret) {
-        JsonVariant **elements = NULL;
-        Link *link;
-        size_t n = 0;
+static int links_append_json(Manager *manager, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+        _cleanup_free_ Link **links = NULL;
+        size_t n_links = 0;
         int r;
 
         assert(manager);
-        assert(ret);
+        assert(v);
 
-        CLEANUP_ARRAY(elements, n, json_variant_unref_many);
+        r = hashmap_dump_sorted(manager->links_by_index, (void***) &links, &n_links);
+        if (r < 0)
+                return r;
 
-        elements = new(JsonVariant*, hashmap_size(manager->links_by_index));
-        if (!elements)
-                return -ENOMEM;
+        FOREACH_ARRAY(link, links, n_links) {
+                _cleanup_(json_variant_unrefp) JsonVariant *e = NULL;
 
-        HASHMAP_FOREACH(link, manager->links_by_index) {
-                r = link_build_json(link, elements + n);
+                r = link_build_json(*link, &e);
                 if (r < 0)
                         return r;
-                n++;
-        }
 
-        typesafe_qsort(elements, n, link_json_compare);
+                r = json_variant_append_array(&array, e);
+                if (r < 0)
+                        return r;
+        }
 
-        return json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("Interfaces", JSON_BUILD_VARIANT_ARRAY(elements, n))));
+        return json_append_one(v, "Interfaces", array);
 }
 
 int manager_build_json(Manager *manager, JsonVariant **ret) {
-        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         int r;
 
         assert(manager);
         assert(ret);
 
-        r = links_build_json(manager, &v);
+        r = links_append_json(manager, &v);
         if (r < 0)
                 return r;
 
-        r = nexthops_build_json(manager->nexthops, &w);
+        r = nexthops_append_json(manager->nexthops, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = routes_build_json(manager->routes, &w);
-        if (r < 0)
-                return r;
-
-        r = json_variant_merge(&v, w);
-        if (r < 0)
-                return r;
-
-        w = json_variant_unref(w);
-
-        r = routing_policy_rules_build_json(manager->rules, &w);
+        r = routes_append_json(manager->routes, &v);
         if (r < 0)
                 return r;
 
-        r = json_variant_merge(&v, w);
+        r = routing_policy_rules_append_json(manager->rules, &v);
         if (r < 0)
                 return r;