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