Extract a string value from JSON properties_json by key. * Writes result to buf (up to buf_sz). Returns buf if found, "" otherwise. * Handles both string values ("key":"value") and numeric values ("key":1.5). */
| 2333 | * Writes result to buf (up to buf_sz). Returns buf if found, "" otherwise. |
| 2334 | * Handles both string values ("key":"value") and numeric values ("key":1.5). */ |
| 2335 | static const char *json_extract_prop(const char *json, const char *key, char *buf, size_t buf_sz) { |
| 2336 | if (!json || !key) { |
| 2337 | buf[0] = '\0'; |
| 2338 | return buf; |
| 2339 | } |
| 2340 | /* Build search pattern: "key": */ |
| 2341 | char pattern[CBM_SZ_256]; |
| 2342 | snprintf(pattern, sizeof(pattern), "\"%s\":", key); |
| 2343 | const char *p = strstr(json, pattern); |
| 2344 | if (!p) { |
| 2345 | buf[0] = '\0'; |
| 2346 | return buf; |
| 2347 | } |
| 2348 | p += strlen(pattern); |
| 2349 | /* Skip whitespace */ |
| 2350 | while (*p == ' ' || *p == '\t') { |
| 2351 | p++; |
| 2352 | } |
| 2353 | if (*p == '"') { |
| 2354 | /* String value — honor backslash escapes: without this, an embedded \" |
| 2355 | * cuts the value short at the first escaped quote. */ |
| 2356 | p++; |
| 2357 | size_t i = 0; |
| 2358 | while (*p && *p != '"' && i < buf_sz - SKIP_ONE) { |
| 2359 | if (*p == '\\' && p[SKIP_ONE] && i + SKIP_ONE < buf_sz - SKIP_ONE) { |
| 2360 | buf[i++] = *p++; /* keep the escape pair intact */ |
| 2361 | } |
| 2362 | buf[i++] = *p++; |
| 2363 | } |
| 2364 | buf[i] = '\0'; |
| 2365 | } else if (*p == '[' || *p == '{') { |
| 2366 | /* Array/object value — copy the whole balanced construct. A scan-to-comma |
| 2367 | * truncates at the first comma INSIDE the value: e.g. a decorators array |
| 2368 | * ["@Roles('OWNER', 'ADMIN')","@Get()"] came back as ["@Roles('OWNER'. */ |
| 2369 | char open = *p; |
| 2370 | char close = (open == '[') ? ']' : '}'; |
| 2371 | int depth = 0; |
| 2372 | int in_str = 0; |
| 2373 | size_t i = 0; |
| 2374 | while (*p && i < buf_sz - SKIP_ONE) { |
| 2375 | char c = *p; |
| 2376 | if (in_str) { |
| 2377 | if (c == '\\' && p[SKIP_ONE] && i + SKIP_ONE < buf_sz - SKIP_ONE) { |
| 2378 | buf[i++] = *p++; /* escape pair stays intact */ |
| 2379 | } else if (c == '"') { |
| 2380 | in_str = 0; |
| 2381 | } |
| 2382 | } else if (c == '"') { |
| 2383 | in_str = 1; |
| 2384 | } else if (c == open) { |
| 2385 | depth++; |
| 2386 | } else if (c == close) { |
| 2387 | depth--; |
| 2388 | } |
| 2389 | buf[i++] = *p++; |
| 2390 | if (!in_str && depth == 0) { |
| 2391 | break; /* outer bracket closed */ |
| 2392 | } |