diff --git a/modules/stats/stats.c b/modules/stats/stats.c
index f162ea1ff97f4ac17a677e37cb6f056edbade250..49b87d5951613c4a186885387b5e4544ca99ee24 100644
--- a/modules/stats/stats.c
+++ b/modules/stats/stats.c
@@ -23,6 +23,7 @@
  */
 
 #include <libknot/packet/pkt.h>
+#include <ccan/json/json.h>
 
 #include "lib/layer/iterate.h"
 #include "lib/rplan.h"
@@ -148,13 +149,9 @@ static char* stats_get(void *env, struct kr_module *module, const char *args)
 
 static int list_entry(const char *key, void *val, void *baton)
 {
-	char **strval = (char **)baton;
+	JsonNode *root = baton;
 	size_t number = (size_t) val;
-	char buf[512];
-	snprintf(buf, sizeof(buf), "'%s': %zu,\n", key, number);
-	char *ret = kr_strcatdup(2, *strval, buf);
-	free(*strval);
-	*strval = ret;
+	json_append_member(root, key, json_mknumber(number));
 	return 0;
 }
 
@@ -166,11 +163,10 @@ static int list_entry(const char *key, void *val, void *baton)
 static char* stats_list(void *env, struct kr_module *module, const char *args)
 {
 	map_t *map = module->data;
-	char *strval = NULL;
-	/* @todo This is very inefficient with memory */
-	map_walk_prefixed(map, args ? args : "", list_entry, &strval);
-	char *ret = kr_strcatdup(3, "{\n", strval, "}");
-	free(strval);
+	JsonNode *root = json_mkobject();
+	map_walk_prefixed(map, args ? args : "", list_entry, root);
+	char *ret = json_encode(root);
+	json_delete(root);
 	return ret;
 }
 
diff --git a/modules/stats/stats.mk b/modules/stats/stats.mk
index fe15669ef097ec8a4228f0c207870298a4f3ea71..362c619a15f395da788cb8f6d96dc2fbe6958329 100644
--- a/modules/stats/stats.mk
+++ b/modules/stats/stats.mk
@@ -1,4 +1,4 @@
-stats_SOURCES := modules/stats/stats.c
+stats_SOURCES := modules/stats/stats.c contrib/ccan/json/json.c
 stats_DEPEND := $(libkresolve)
 stats_LIBS := $(libkresolve_TARGET) $(libkresolve_LIBS)
 $(call make_c_module,stats)