From: Adrian Vovk Date: Wed, 3 Jul 2024 21:57:42 +0000 (-0400) Subject: table: Improve mangling of JSON field names X-Git-Tag: v257-rc1~892^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c4cb49eb342bb347fb974eb55236d2eb3e565086;p=thirdparty%2Fsystemd.git table: Improve mangling of JSON field names First, when displaying JSON we convert dashes into underscores. We want to avoid using dashes in JSON field names in new code, because some JSON parsers don't support dashes very well. Second, we make the first character of every word lower-case. This better matches our JSON field name style, and makes the automatic JSON name mangling a lot more useful for vertical tables, where fields are given a display name. For example, "Foo Bar" would be converted into "foo_bar" instead of "Foo_Bar", which much better matches our style. We don't make the whole string lowercase to support cases like: "fooBar" should stay as "fooBar". Some situations don't behave quite perfectly, such as "Foo BarBaz" gets converted into "foo_barBaz", or all-caps headings get mangled incorrectly. In these situations, the JSON field should be overridden manually. In most cases, or at least more cases than before, this heuristic does good enough. --- diff --git a/src/shared/format-table.c b/src/shared/format-table.c index e538d964147..ee641d11444 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 15c0547b944..bb2eb707599 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 fa34c29b02c..41621dbb5b3 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;