diff --git a/src/shared/format-table.c b/src/shared/format-table.c index e538d96414..ee641d1144 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2895,18 +2895,39 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) { } } -static char* string_to_json_field_name(const char *f) { +char* table_mangle_to_json_field_name(const char *str) { /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a - * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to - * underscores and leave everything as is. */ + * field name can be hence this is a bit vague and black magic. Here's what we do: + * - Convert spaces to underscores + * - Convert dashes to underscores (some JSON parsers don't like dealing with dashes) + * - Convert most other symbols to underscores (for similar reasons) + * - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase) + */ - char *c = strdup(f); + bool new_word = true; + char *c; + + assert(str); + + c = strdup(str); if (!c) return NULL; - for (char *x = c; *x; x++) - if (isspace(*x)) + for (char *x = c; *x; x++) { + if (!strchr(ALPHANUMERICAL, *x)) { *x = '_'; + new_word = true; + continue; + } + + if (new_word) { + if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case + * then we assume the whole word is all-caps + * and avoid lowercasing it. */ + *x = ascii_tolower(*x); + new_word = false; + } + } return c; } @@ -2927,7 +2948,7 @@ static int table_make_json_field_name(Table *t, TableData *d, char **ret) { return -ENOMEM; } - mangled = string_to_json_field_name(n); + mangled = table_mangle_to_json_field_name(n); if (!mangled) return -ENOMEM; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 15c0547b94..bb2eb70759 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -166,6 +166,7 @@ int table_print_json(Table *t, FILE *f, sd_json_format_flags_t json_flags); int table_print_with_pager(Table *t, sd_json_format_flags_t json_format_flags, PagerFlags pager_flags, bool show_header); +char* table_mangle_to_json_field_name(const char *str); int table_set_json_field_name(Table *t, size_t idx, const char *name); #define table_log_add_error(r) \ diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index fa34c29b02..41621dbb5b 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -404,6 +404,38 @@ TEST(json) { assert_se(sd_json_variant_equal(v, w)); } +TEST(json_mangling) { + static const struct { + const char *arg; + const char *exp; + } cases[] = { + /* Not Mangled */ + { "foo", "foo" }, + { "foo_bar", "foo_bar" }, + { "fooBar", "fooBar" }, + { "fooBar123", "fooBar123" }, + { "foo_bar123", "foo_bar123" }, + { ALPHANUMERICAL, ALPHANUMERICAL }, + { "_123", "_123" }, + + /* Mangled */ + { "Foo Bar", "foo_bar" }, + { "Foo-Bar", "foo_bar" }, + { "Foo@Bar", "foo_bar" }, + { "Foo (Bar)", "foo__bar_"}, + { "MixedCase ALLCAPS", "mixedCase_ALLCAPS" }, + { "_X", "_x" }, + { "_Foo", "_foo" }, + }; + + FOREACH_ELEMENT(i, cases) { + _cleanup_free_ char *ret = NULL; + assert_se(ret = table_mangle_to_json_field_name(i->arg)); + printf("\"%s\" -> \"%s\"\n", i->arg, ret); + assert_se(streq(ret, i->exp)); + } +} + TEST(table) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *formatted = NULL;