Line data Source code
1 : #include "json_util.h"
2 : #include <stdlib.h>
3 : #include <string.h>
4 : #include <ctype.h>
5 :
6 : /* ── Internal helpers ───────────────────────────────────────────────── */
7 :
8 : /** Skip whitespace, return pointer to first non-ws char. */
9 60157 : static const char *skip_ws(const char *p) {
10 60269 : while (*p && isspace((unsigned char)*p)) p++;
11 60157 : return p;
12 : }
13 :
14 : /**
15 : * Skip a JSON value starting at *p (string, number, object, array,
16 : * true/false/null). Returns pointer past the value, or NULL on error.
17 : */
18 8011 : static const char *skip_value(const char *p) {
19 8011 : p = skip_ws(p);
20 8011 : if (!*p) return NULL;
21 :
22 8011 : if (*p == '"') {
23 : /* String: advance past closing quote, handling escapes */
24 4026 : p++;
25 60775 : while (*p && *p != '"') {
26 56749 : if (*p == '\\') { p++; if (!*p) return NULL; }
27 56749 : p++;
28 : }
29 4026 : return *p == '"' ? p + 1 : NULL;
30 : }
31 3985 : if (*p == '{') {
32 : /* Object: match braces */
33 2798 : int depth = 1; p++;
34 27701 : while (*p && depth > 0) {
35 24903 : if (*p == '{') depth++;
36 24884 : else if (*p == '}') depth--;
37 22067 : else if (*p == '"') {
38 12404 : p++;
39 120093 : while (*p && *p != '"') {
40 107689 : if (*p == '\\') { p++; if (!*p) return NULL; }
41 107689 : p++;
42 : }
43 12404 : if (!*p) return NULL;
44 : }
45 24903 : p++;
46 : }
47 2798 : return depth == 0 ? p : NULL;
48 : }
49 1187 : if (*p == '[') {
50 : /* Array: match brackets */
51 1020 : int depth = 1; p++;
52 47347 : while (*p && depth > 0) {
53 46327 : if (*p == '[') depth++;
54 46315 : else if (*p == ']') depth--;
55 45283 : else if (*p == '"') {
56 18801 : p++;
57 208435 : while (*p && *p != '"') {
58 189634 : if (*p == '\\') { p++; if (!*p) return NULL; }
59 189634 : p++;
60 : }
61 18801 : if (!*p) return NULL;
62 : }
63 46327 : p++;
64 : }
65 1020 : return depth == 0 ? p : NULL;
66 : }
67 : /* Number, true, false, null: advance past alphanumeric + signs */
68 396 : while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
69 167 : *p == '+' || *p == '-'))
70 229 : p++;
71 167 : return p;
72 : }
73 :
74 : /**
75 : * Find a key at the current object nesting level.
76 : * p must point inside a '{' ... '}' block (past the opening '{').
77 : * Returns pointer to the value (after the ':' and whitespace), or NULL.
78 : */
79 5490 : static const char *find_key(const char *p, const char *key) {
80 5490 : size_t klen = strlen(key);
81 5490 : p = skip_ws(p);
82 :
83 10706 : while (*p && *p != '}') {
84 : /* Expect a quoted key */
85 10615 : if (*p != '"') return NULL;
86 10615 : p++;
87 10615 : const char *ks = p;
88 60041 : while (*p && *p != '"') {
89 49426 : if (*p == '\\') { p++; if (!*p) return NULL; }
90 49426 : p++;
91 : }
92 10615 : if (!*p) return NULL;
93 10615 : size_t found_len = (size_t)(p - ks);
94 10615 : int match = (found_len == klen && memcmp(ks, key, klen) == 0);
95 10615 : p++; /* past closing quote */
96 :
97 10615 : p = skip_ws(p);
98 10615 : if (*p != ':') return NULL;
99 10615 : p++;
100 10615 : p = skip_ws(p);
101 :
102 10615 : if (match) return p;
103 :
104 : /* Skip the value */
105 5216 : p = skip_value(p);
106 5216 : if (!p) return NULL;
107 5216 : p = skip_ws(p);
108 5216 : if (*p == ',') p++;
109 5216 : p = skip_ws(p);
110 : }
111 91 : return NULL;
112 : }
113 :
114 : /**
115 : * Unescape a JSON string from src[0..len-1] into a heap-allocated buffer.
116 : * Handles: \\, \", \/, \n, \r, \t, \b, \f, \uXXXX (BMP only, as ASCII ?).
117 : */
118 5825 : static char *unescape(const char *src, size_t len) {
119 5825 : char *buf = malloc(len + 1);
120 5825 : if (!buf) return NULL;
121 5825 : size_t out = 0;
122 311314 : for (size_t i = 0; i < len; i++) {
123 305489 : if (src[i] == '\\' && i + 1 < len) {
124 14 : i++;
125 14 : switch (src[i]) {
126 2 : case '"': buf[out++] = '"'; break;
127 1 : case '\\': buf[out++] = '\\'; break;
128 1 : case '/': buf[out++] = '/'; break;
129 2 : case 'n': buf[out++] = '\n'; break;
130 1 : case 'r': buf[out++] = '\r'; break;
131 2 : case 't': buf[out++] = '\t'; break;
132 1 : case 'b': buf[out++] = '\b'; break;
133 1 : case 'f': buf[out++] = '\f'; break;
134 2 : case 'u':
135 : /* \uXXXX — emit '?' for non-ASCII, decode ASCII range */
136 2 : if (i + 4 < len) {
137 2 : char hex[5] = {src[i+1], src[i+2], src[i+3], src[i+4], 0};
138 2 : unsigned long cp = strtoul(hex, NULL, 16);
139 2 : if (cp < 0x80)
140 1 : buf[out++] = (char)cp;
141 : else
142 1 : buf[out++] = '?'; /* non-ASCII BMP placeholder */
143 2 : i += 4;
144 : }
145 2 : break;
146 1 : default:
147 1 : buf[out++] = src[i];
148 1 : break;
149 : }
150 : } else {
151 305475 : buf[out++] = src[i];
152 : }
153 : }
154 5825 : buf[out] = '\0';
155 5825 : return buf;
156 : }
157 :
158 : /* ── Public API ─────────────────────────────────────────────────────── */
159 :
160 6 : char *json_get_nested_string(const char *json,
161 : const char *outer_key, const char *inner_key) {
162 6 : if (!json || !outer_key || !inner_key) return NULL;
163 :
164 6 : const char *p = skip_ws(json);
165 6 : if (*p != '{') return NULL;
166 6 : p++;
167 :
168 6 : const char *outer_val = find_key(p, outer_key);
169 6 : if (!outer_val) return NULL;
170 6 : outer_val = skip_ws(outer_val);
171 6 : if (*outer_val != '{') return NULL;
172 6 : outer_val++; /* inside the sub-object */
173 :
174 6 : const char *inner_val = find_key(outer_val, inner_key);
175 6 : if (!inner_val || *inner_val != '"') return NULL;
176 6 : inner_val++; /* past opening quote */
177 :
178 6 : const char *end = inner_val;
179 102 : while (*end && *end != '"') {
180 96 : if (*end == '\\') { end++; if (!*end) return NULL; }
181 96 : end++;
182 : }
183 6 : if (!*end) return NULL;
184 :
185 6 : return unescape(inner_val, (size_t)(end - inner_val));
186 : }
187 :
188 4456 : char *json_get_string(const char *json, const char *key) {
189 4456 : if (!json || !key) return NULL;
190 :
191 4454 : const char *p = skip_ws(json);
192 4454 : if (*p != '{') return NULL;
193 4454 : p++;
194 :
195 4454 : const char *val = find_key(p, key);
196 4454 : if (!val || *val != '"') return NULL;
197 :
198 4369 : val++; /* past opening quote */
199 4369 : const char *end = val;
200 301632 : while (*end && *end != '"') {
201 297263 : if (*end == '\\') { end++; if (!*end) return NULL; }
202 297263 : end++;
203 : }
204 4369 : if (!*end) return NULL;
205 :
206 4369 : return unescape(val, (size_t)(end - val));
207 : }
208 :
209 6 : int json_get_int(const char *json, const char *key, int *out) {
210 6 : if (!json || !key || !out) return -1;
211 :
212 5 : const char *p = skip_ws(json);
213 5 : if (*p != '{') return -1;
214 5 : p++;
215 :
216 5 : const char *val = find_key(p, key);
217 5 : if (!val) return -1;
218 :
219 : /* Accept number or quoted number */
220 4 : if (*val == '"') {
221 1 : val++;
222 : char *end;
223 1 : long v = strtol(val, &end, 10);
224 1 : if (end == val) return -1;
225 1 : *out = (int)v;
226 1 : return 0;
227 : }
228 3 : if (*val == '-' || isdigit((unsigned char)*val)) {
229 : char *end;
230 2 : long v = strtol(val, &end, 10);
231 2 : if (end == val) return -1;
232 2 : *out = (int)v;
233 2 : return 0;
234 : }
235 1 : return -1;
236 : }
237 :
238 833 : int json_get_string_array(const char *json, const char *key,
239 : char ***out, int *count_out) {
240 833 : if (!json || !key || !out || !count_out) return -1;
241 833 : *out = NULL;
242 833 : *count_out = 0;
243 :
244 833 : const char *p = skip_ws(json);
245 833 : if (*p != '{') return -1;
246 833 : p++;
247 :
248 833 : const char *val = find_key(p, key);
249 833 : if (!val || *val != '[') return -1;
250 :
251 832 : val++; /* past '[' */
252 832 : val = skip_ws(val);
253 :
254 832 : int cap = 8;
255 832 : char **arr = malloc((size_t)cap * sizeof(char *));
256 832 : if (!arr) return -1;
257 832 : int count = 0;
258 :
259 2282 : while (*val && *val != ']') {
260 1450 : if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
261 1450 : val++; /* past opening quote */
262 1450 : const char *end = val;
263 9588 : while (*end && *end != '"') {
264 8138 : if (*end == '\\') { end++; if (!*end) goto fail; }
265 8138 : end++;
266 : }
267 1450 : if (!*end) goto fail;
268 :
269 1450 : char *s = unescape(val, (size_t)(end - val));
270 1450 : if (!s) goto fail;
271 :
272 1450 : if (count >= cap) {
273 2 : cap *= 2;
274 2 : char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
275 2 : if (!tmp) { free(s); goto fail; }
276 2 : arr = tmp;
277 : }
278 1450 : arr[count++] = s;
279 :
280 1450 : val = end + 1; /* past closing quote */
281 1450 : val = skip_ws(val);
282 1450 : if (*val == ',') val++;
283 1450 : val = skip_ws(val);
284 : }
285 :
286 832 : *out = arr;
287 832 : *count_out = count;
288 832 : return 0;
289 :
290 0 : fail:
291 0 : for (int i = 0; i < count; i++) free(arr[i]);
292 0 : free(arr);
293 0 : return -1;
294 : }
295 :
296 186 : int json_foreach_object(const char *json, const char *key,
297 : JsonObjectCb cb, void *ctx) {
298 186 : if (!json || !key || !cb) return -1;
299 :
300 186 : const char *p = skip_ws(json);
301 186 : if (*p != '{') return -1;
302 186 : p++;
303 :
304 186 : const char *val = find_key(p, key);
305 186 : if (!val || *val != '[') return -1;
306 :
307 182 : val++; /* past '[' */
308 182 : val = skip_ws(val);
309 182 : int index = 0;
310 :
311 2977 : while (*val && *val != ']') {
312 2795 : if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
313 :
314 : /* Find the extent of this object */
315 2795 : const char *obj_start = val;
316 2795 : const char *obj_end = skip_value(val);
317 2795 : if (!obj_end) return -1;
318 :
319 : /* Create a NUL-terminated copy for the callback */
320 2795 : size_t obj_len = (size_t)(obj_end - obj_start);
321 2795 : char *obj_copy = malloc(obj_len + 1);
322 2795 : if (!obj_copy) return -1;
323 2795 : memcpy(obj_copy, obj_start, obj_len);
324 2795 : obj_copy[obj_len] = '\0';
325 :
326 2795 : cb(obj_copy, index, ctx);
327 2795 : free(obj_copy);
328 2795 : index++;
329 :
330 2795 : val = skip_ws(obj_end);
331 2795 : if (*val == ',') val++;
332 2795 : val = skip_ws(val);
333 : }
334 :
335 182 : return index;
336 : }
|