From 28b83c625d2fa22c992d64541637792c4f1c4ef2 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Wed, 30 Oct 2024 12:29:54 -0400 Subject: [PATCH] rg_utils: Added rg_json_fixup to fix json errors In order to provide a better user experience, rg_json_fixup strips trailing commas. In the future it will also strip comments. NOTE: The way it finds trailing commas is certainly naive and could break things, so we should always try to parse the raw string before falling back to rg_json_fixup! --- components/retro-go/rg_gui.c | 8 +++++--- components/retro-go/rg_settings.c | 8 +++++--- components/retro-go/rg_utils.c | 19 +++++++++++++++++++ components/retro-go/rg_utils.h | 10 +++++++++- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 4c32695e7..4fadddf50 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -118,11 +118,13 @@ bool rg_gui_set_theme(const char *theme_name) if (theme_name && theme_name[0]) { snprintf(pathbuf, RG_PATH_MAX, "%s/%s/theme.json", RG_BASE_PATH_THEMES, theme_name); - void *data; + char *data; size_t data_len; - if (rg_storage_read_file(pathbuf, &data, &data_len, 0)) + if (rg_storage_read_file(pathbuf, (void **)&data, &data_len, 0)) { - new_theme = cJSON_Parse((char *)data); + new_theme = cJSON_Parse(data); + if (!new_theme) // Parse failure, clean the markup and try again + new_theme = cJSON_Parse(rg_json_fixup(data)); free(data); } if (!new_theme) diff --git a/components/retro-go/rg_settings.c b/components/retro-go/rg_settings.c index 8f80f4d78..6ec717b59 100644 --- a/components/retro-go/rg_settings.c +++ b/components/retro-go/rg_settings.c @@ -31,12 +31,14 @@ static cJSON *json_root(const char *name, bool set_dirty) cJSON_AddStringToObject(branch, "namespace", name); cJSON_AddNumberToObject(branch, "changed", 0); - void *data; size_t data_len; + char *data; size_t data_len; char pathbuf[RG_PATH_MAX]; snprintf(pathbuf, RG_PATH_MAX, "%s/%s.json", RG_BASE_PATH_CONFIG, name); - if (rg_storage_read_file(pathbuf, &data, &data_len, 0)) + if (rg_storage_read_file(pathbuf, (void **)&data, &data_len, 0)) { - cJSON *values = cJSON_Parse((char *)data); + cJSON *values = cJSON_Parse(data); + if (!values) // Parse failure, clean the markup and try again + values = cJSON_Parse(rg_json_fixup(data)); if (values) { RG_LOGI("Config file loaded: '%s'", pathbuf); diff --git a/components/retro-go/rg_utils.c b/components/retro-go/rg_utils.c index 59a57da28..d3d032d8d 100644 --- a/components/retro-go/rg_utils.c +++ b/components/retro-go/rg_utils.c @@ -29,6 +29,25 @@ char *rg_strtoupper(char *str) return str; } +char *rg_json_fixup(char *json) +{ + // Strip trailing commas, eg [1,2,3,] {"a":1,} + for (char *ptr = json, *prev = ptr; ptr && *ptr; ++ptr) + { + if ((*ptr == '}' || *ptr == ']' || *ptr == ',') && *prev == ',') + { + RG_LOGW("Found trailing comma at pos %d", (int)(ptr - json)); + *prev = ' '; + } + if (*ptr != '\t' && *ptr != '\n' && *ptr != ' ') + prev = ptr; + } + + // TODO: We should also strip C-style comments! + + return json; +} + const char *rg_dirname(const char *path) { static char buffer[100]; diff --git a/components/retro-go/rg_utils.h b/components/retro-go/rg_utils.h index 224e548f2..325442fa7 100644 --- a/components/retro-go/rg_utils.h +++ b/components/retro-go/rg_utils.h @@ -40,6 +40,8 @@ #define PRINTF_BINARY_32 PRINTF_BINARY_16 " " PRINTF_BINARY_16 #define PRINTF_BINVAL_32(i) PRINTF_BINVAL_16((i) >> 16), PRINTF_BINVAL_16(i) +/* String functions */ + /** * both functions give you an allocation of strlen(str) + 1 valid for the lifetime of the application * they cannot be freed. unique avoids keeping multiple copies of an identical string (eg a path) @@ -47,16 +49,22 @@ */ const char *rg_const_string(const char *str); const char *rg_unique_string(const char *str); - char *rg_strtolower(char *str); char *rg_strtoupper(char *str); +char *rg_json_fixup(char *json); + +/* Paths functions */ const char *rg_dirname(const char *path); const char *rg_basename(const char *path); const char *rg_extension(const char *filename); bool rg_extension_match(const char *filename, const char *extensions); const char *rg_relpath(const char *path); + +/* Hashing */ uint32_t rg_crc32(uint32_t crc, const uint8_t *buf, size_t len); uint32_t rg_hash(const char *buf, size_t len); + +/* Misc */ void *rg_alloc(size_t size, uint32_t caps); // rg_usleep behaves like usleep in libc: it will sleep for *at least* `us` microseconds, but possibly more // due to scheduling. You should use rg_task_delay() if you don't need more than 10-15ms granularity.