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 58451 : static const char *skip_ws(const char *p) {
10 58451 : while (*p && isspace((unsigned char)*p)) p++;
11 58451 : 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 7827 : static const char *skip_value(const char *p) {
19 7827 : p = skip_ws(p);
20 7827 : if (!*p) return NULL;
21 :
22 7827 : if (*p == '"') {
23 : /* String: advance past closing quote, handling escapes */
24 3958 : p++;
25 60167 : while (*p && *p != '"') {
26 56209 : if (*p == '\\') { p++; if (!*p) return NULL; }
27 56209 : p++;
28 : }
29 3958 : return *p == '"' ? p + 1 : NULL;
30 : }
31 3869 : if (*p == '{') {
32 : /* Object: match braces */
33 2740 : int depth = 1; p++;
34 27104 : while (*p && depth > 0) {
35 24364 : if (*p == '{') depth++;
36 24358 : else if (*p == '}') depth--;
37 21612 : else if (*p == '"') {
38 12171 : p++;
39 118572 : while (*p && *p != '"') {
40 106401 : if (*p == '\\') { p++; if (!*p) return NULL; }
41 106401 : p++;
42 : }
43 12171 : if (!*p) return NULL;
44 : }
45 24364 : p++;
46 : }
47 2740 : return depth == 0 ? p : NULL;
48 : }
49 1129 : if (*p == '[') {
50 : /* Array: match brackets */
51 982 : int depth = 1; p++;
52 46856 : while (*p && depth > 0) {
53 45874 : if (*p == '[') depth++;
54 45865 : else if (*p == ']') depth--;
55 44874 : else if (*p == '"') {
56 18625 : p++;
57 207122 : while (*p && *p != '"') {
58 188497 : if (*p == '\\') { p++; if (!*p) return NULL; }
59 188497 : p++;
60 : }
61 18625 : if (!*p) return NULL;
62 : }
63 45874 : p++;
64 : }
65 982 : return depth == 0 ? p : NULL;
66 : }
67 : /* Number, true, false, null: advance past alphanumeric + signs */
68 356 : while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
69 147 : *p == '+' || *p == '-'))
70 209 : p++;
71 147 : 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 5292 : static const char *find_key(const char *p, const char *key) {
80 5292 : size_t klen = strlen(key);
81 5292 : p = skip_ws(p);
82 :
83 10379 : while (*p && *p != '}') {
84 : /* Expect a quoted key */
85 10307 : if (*p != '"') return NULL;
86 10307 : p++;
87 10307 : const char *ks = p;
88 58019 : while (*p && *p != '"') {
89 47712 : if (*p == '\\') { p++; if (!*p) return NULL; }
90 47712 : p++;
91 : }
92 10307 : if (!*p) return NULL;
93 10307 : size_t found_len = (size_t)(p - ks);
94 10307 : int match = (found_len == klen && memcmp(ks, key, klen) == 0);
95 10307 : p++; /* past closing quote */
96 :
97 10307 : p = skip_ws(p);
98 10307 : if (*p != ':') return NULL;
99 10307 : p++;
100 10307 : p = skip_ws(p);
101 :
102 10307 : if (match) return p;
103 :
104 : /* Skip the value */
105 5087 : p = skip_value(p);
106 5087 : if (!p) return NULL;
107 5087 : p = skip_ws(p);
108 5087 : if (*p == ',') p++;
109 5087 : p = skip_ws(p);
110 : }
111 72 : 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 5652 : static char *unescape(const char *src, size_t len) {
119 5652 : char *buf = malloc(len + 1);
120 5652 : if (!buf) return NULL;
121 5652 : size_t out = 0;
122 307943 : for (size_t i = 0; i < len; i++) {
123 302291 : if (src[i] == '\\' && i + 1 < len) {
124 0 : i++;
125 0 : switch (src[i]) {
126 0 : case '"': buf[out++] = '"'; break;
127 0 : case '\\': buf[out++] = '\\'; break;
128 0 : case '/': buf[out++] = '/'; break;
129 0 : case 'n': buf[out++] = '\n'; break;
130 0 : case 'r': buf[out++] = '\r'; break;
131 0 : case 't': buf[out++] = '\t'; break;
132 0 : case 'b': buf[out++] = '\b'; break;
133 0 : case 'f': buf[out++] = '\f'; break;
134 0 : case 'u':
135 : /* \uXXXX — emit '?' for non-ASCII, decode ASCII range */
136 0 : if (i + 4 < len) {
137 0 : char hex[5] = {src[i+1], src[i+2], src[i+3], src[i+4], 0};
138 0 : unsigned long cp = strtoul(hex, NULL, 16);
139 0 : if (cp < 0x80)
140 0 : buf[out++] = (char)cp;
141 : else
142 0 : buf[out++] = '?'; /* non-ASCII BMP placeholder */
143 0 : i += 4;
144 : }
145 0 : break;
146 0 : default:
147 0 : buf[out++] = src[i];
148 0 : break;
149 : }
150 : } else {
151 302291 : buf[out++] = src[i];
152 : }
153 : }
154 5652 : buf[out] = '\0';
155 5652 : return buf;
156 : }
157 :
158 : /* ── Public API ─────────────────────────────────────────────────────── */
159 :
160 2 : char *json_get_nested_string(const char *json,
161 : const char *outer_key, const char *inner_key) {
162 2 : if (!json || !outer_key || !inner_key) return NULL;
163 :
164 2 : const char *p = skip_ws(json);
165 2 : if (*p != '{') return NULL;
166 2 : p++;
167 :
168 2 : const char *outer_val = find_key(p, outer_key);
169 2 : if (!outer_val) return NULL;
170 2 : outer_val = skip_ws(outer_val);
171 2 : if (*outer_val != '{') return NULL;
172 2 : outer_val++; /* inside the sub-object */
173 :
174 2 : const char *inner_val = find_key(outer_val, inner_key);
175 2 : if (!inner_val || *inner_val != '"') return NULL;
176 2 : inner_val++; /* past opening quote */
177 :
178 2 : const char *end = inner_val;
179 34 : while (*end && *end != '"') {
180 32 : if (*end == '\\') { end++; if (!*end) return NULL; }
181 32 : end++;
182 : }
183 2 : if (!*end) return NULL;
184 :
185 2 : return unescape(inner_val, (size_t)(end - inner_val));
186 : }
187 :
188 4317 : char *json_get_string(const char *json, const char *key) {
189 4317 : if (!json || !key) return NULL;
190 :
191 4317 : const char *p = skip_ws(json);
192 4317 : if (*p != '{') return NULL;
193 4317 : p++;
194 :
195 4317 : const char *val = find_key(p, key);
196 4317 : if (!val || *val != '"') return NULL;
197 :
198 4248 : val++; /* past opening quote */
199 4248 : const char *end = val;
200 298651 : while (*end && *end != '"') {
201 294403 : if (*end == '\\') { end++; if (!*end) return NULL; }
202 294403 : end++;
203 : }
204 4248 : if (!*end) return NULL;
205 :
206 4248 : return unescape(val, (size_t)(end - val));
207 : }
208 :
209 0 : int json_get_int(const char *json, const char *key, int *out) {
210 0 : if (!json || !key || !out) return -1;
211 :
212 0 : const char *p = skip_ws(json);
213 0 : if (*p != '{') return -1;
214 0 : p++;
215 :
216 0 : const char *val = find_key(p, key);
217 0 : if (!val) return -1;
218 :
219 : /* Accept number or quoted number */
220 0 : if (*val == '"') {
221 0 : val++;
222 : char *end;
223 0 : long v = strtol(val, &end, 10);
224 0 : if (end == val) return -1;
225 0 : *out = (int)v;
226 0 : return 0;
227 : }
228 0 : if (*val == '-' || isdigit((unsigned char)*val)) {
229 : char *end;
230 0 : long v = strtol(val, &end, 10);
231 0 : if (end == val) return -1;
232 0 : *out = (int)v;
233 0 : return 0;
234 : }
235 0 : return -1;
236 : }
237 :
238 816 : int json_get_string_array(const char *json, const char *key,
239 : char ***out, int *count_out) {
240 816 : if (!json || !key || !out || !count_out) return -1;
241 816 : *out = NULL;
242 816 : *count_out = 0;
243 :
244 816 : const char *p = skip_ws(json);
245 816 : if (*p != '{') return -1;
246 816 : p++;
247 :
248 816 : const char *val = find_key(p, key);
249 816 : if (!val || *val != '[') return -1;
250 :
251 816 : val++; /* past '[' */
252 816 : val = skip_ws(val);
253 :
254 816 : int cap = 8;
255 816 : char **arr = malloc((size_t)cap * sizeof(char *));
256 816 : if (!arr) return -1;
257 816 : int count = 0;
258 :
259 2218 : while (*val && *val != ']') {
260 1402 : if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
261 1402 : val++; /* past opening quote */
262 1402 : const char *end = val;
263 9258 : while (*end && *end != '"') {
264 7856 : if (*end == '\\') { end++; if (!*end) goto fail; }
265 7856 : end++;
266 : }
267 1402 : if (!*end) goto fail;
268 :
269 1402 : char *s = unescape(val, (size_t)(end - val));
270 1402 : if (!s) goto fail;
271 :
272 1402 : if (count >= cap) {
273 0 : cap *= 2;
274 0 : char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
275 0 : if (!tmp) { free(s); goto fail; }
276 0 : arr = tmp;
277 : }
278 1402 : arr[count++] = s;
279 :
280 1402 : val = end + 1; /* past closing quote */
281 1402 : val = skip_ws(val);
282 1402 : if (*val == ',') val++;
283 1402 : val = skip_ws(val);
284 : }
285 :
286 816 : *out = arr;
287 816 : *count_out = count;
288 816 : 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 155 : int json_foreach_object(const char *json, const char *key,
297 : JsonObjectCb cb, void *ctx) {
298 155 : if (!json || !key || !cb) return -1;
299 :
300 155 : const char *p = skip_ws(json);
301 155 : if (*p != '{') return -1;
302 155 : p++;
303 :
304 155 : const char *val = find_key(p, key);
305 155 : if (!val || *val != '[') return -1;
306 :
307 152 : val++; /* past '[' */
308 152 : val = skip_ws(val);
309 152 : int index = 0;
310 :
311 2892 : while (*val && *val != ']') {
312 2740 : if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
313 :
314 : /* Find the extent of this object */
315 2740 : const char *obj_start = val;
316 2740 : const char *obj_end = skip_value(val);
317 2740 : if (!obj_end) return -1;
318 :
319 : /* Create a NUL-terminated copy for the callback */
320 2740 : size_t obj_len = (size_t)(obj_end - obj_start);
321 2740 : char *obj_copy = malloc(obj_len + 1);
322 2740 : if (!obj_copy) return -1;
323 2740 : memcpy(obj_copy, obj_start, obj_len);
324 2740 : obj_copy[obj_len] = '\0';
325 :
326 2740 : cb(obj_copy, index, ctx);
327 2740 : free(obj_copy);
328 2740 : index++;
329 :
330 2740 : val = skip_ws(obj_end);
331 2740 : if (*val == ',') val++;
332 2740 : val = skip_ws(val);
333 : }
334 :
335 152 : return index;
336 : }
|