Adds json_pack / json_unpack variadic functions.
[jansson.git] / src / variadic.c
1 /*
2  * Copyright (c) 2009, 2010 Petri Lehtinen <petri@digip.org>
3  * Copyright (c) 2010 Graeme Smecher <graeme.smecher@mail.mcgill.ca>
4  *
5  * Jansson is free software; you can redistribute it and/or modify
6  * it under the terms of the MIT license. See LICENSE for details.
7  */
8
9 #include <stdarg.h>
10 #include <string.h>
11 #include <assert.h>
12
13 #include <jansson.h>
14 #include "jansson_private.h"
15
16 static void error_init(json_error_t **error)
17 {
18     if(error)
19         *error = NULL;
20 }
21
22 static void error_set(json_error_t **error, const int line, const char *msg, ...)
23 {
24     va_list ap;
25
26     if(!error || *error)
27         return;
28
29     *error = calloc(1, sizeof(json_error_t));
30     if(!*error)
31         return;
32
33     va_start(ap, msg);
34     vsnprintf((*error)->msg, JSON_ERROR_MSG_LENGTH, msg, ap);
35     va_end(ap);
36
37     va_start(ap, msg);
38     vfprintf(stderr, msg, ap);
39     va_end(ap);
40
41     (*error)->line = line;
42 }
43
44 json_t *json_pack(json_error_t **error, const char *fmt, ...) {
45     int fmt_length = strlen(fmt);
46     va_list ap;
47
48     /* Keep a stack of containers (lists and objects) */
49     int depth = 0;
50     json_t **stack = NULL;
51
52     /* Keep a list of objects we create in case of error */
53     int free_count = 0;
54     json_t **free_list = NULL;
55
56     json_t *cur = NULL; /* Current container */
57     json_t *root = NULL; /* root object */
58     json_t *obj = NULL;
59
60     char *key = NULL; /* Current key in an object */
61     char *s;
62
63     int line = 1;
64
65     /* Allocation provisioned for worst case */
66     stack = calloc(fmt_length, sizeof(json_t *));
67     free_list = calloc(fmt_length, sizeof(json_t *));
68
69     error_init(error);
70
71     if(!stack || !free_list)
72         goto out;
73
74     va_start(ap, fmt);
75     while(*fmt) {
76         switch(*fmt) {
77             case '\n':
78                 line++;
79                 break;
80
81             case ' ': /* Whitespace */
82                 break;
83
84             case ',': /* Element spacer */
85                 if(!root)
86                 {
87                     error_set(error, line,
88                               "Unexpected COMMA precedes root element!");
89                     root = NULL;
90                     goto out;
91                 }
92
93                 if(!cur)
94                 {
95                     error_set(error, line,
96                               "Unexpected COMMA outside a list or object!");
97                     root = NULL;
98                     goto out;
99                 }
100
101                 if(key)
102                 {
103                     error_set(error, line, "Expected KEY, got COMMA!");
104                     root = NULL;
105                     goto out;
106                 }
107                 break;
108
109             case ':': /* Key/value separator */
110                 if(!key)
111                 {
112                     error_set(error, line, "Got key/value separator without "
113                             "a key preceding it!");
114                     root = NULL;
115                     goto out;
116                 }
117
118                 if(!json_is_object(cur))
119                 {
120                     error_set(error, line, "Got a key/value separator "
121                             "(':') outside an object!");
122                     root = NULL;
123                     goto out;
124                 }
125
126                 break;
127
128             case ']': /* Close array or object */
129             case '}':
130
131                 if(key)
132                 {
133                     error_set(error, line, "OBJECT or ARRAY ended with an "
134                               "incomplete key/value pair!");
135                     root = NULL;
136                     goto out;
137                 }
138
139                 if(depth <= 0)
140                 {
141                     error_set(error, line,
142                               "Too many close-brackets '%c'", *fmt);
143                     root = NULL;
144                     goto out;
145                 }
146
147                 if(*fmt == ']' && !json_is_array(cur))
148                 {
149                     error_set(error, line,
150                               "Stray close-array ']' character");
151                     root = NULL;
152                     goto out;
153                 }
154
155                 if(*fmt == '}' && !json_is_object(cur))
156                 {
157                     error_set(error, line,
158                               "Stray close-object '}' character");
159                     root = NULL;
160                     goto out;
161                 }
162
163                 cur = stack[--depth];
164                 break;
165
166             case '[':
167                 obj = json_array();
168                 goto obj_common;
169
170             case '{':
171                 obj = json_object();
172                 goto obj_common;
173
174             case 's': /* string */
175                 s = va_arg(ap, char*);
176
177                 if(!s)
178                 {
179                     error_set(error, line,
180                               "Refusing to handle a NULL string");
181                     root = NULL;
182                     goto out;
183                 }
184
185                 if(json_is_object(cur) && !key)
186                 {
187                     /* It's a key */
188                     key = s;
189                     break;
190                 }
191
192                 obj = json_string(s);
193                 goto obj_common;
194
195             case 'n': /* null */
196                 obj = json_null();
197                 goto obj_common;
198
199             case 'b': /* boolean */
200                 obj = va_arg(ap, int) ?
201                     json_true() : json_false();
202                 goto obj_common;
203
204             case 'i': /* integer */
205                 obj = json_integer(va_arg(ap, int));
206                 goto obj_common;
207
208             case 'f': /* double-precision float */
209                 obj = json_real(va_arg(ap, double));
210                 goto obj_common;
211
212             case 'O': /* a json_t object; increments refcount */
213                 obj = va_arg(ap, json_t *);
214                 json_incref(obj);
215                 goto obj_common;
216
217             case 'o': /* a json_t object; doesn't increment refcount */
218                 obj = va_arg(ap, json_t *);
219                 goto obj_common;
220
221 obj_common:     free_list[free_count++] = obj;
222
223                 /* Root this object to its parent */
224                 if(json_is_object(cur)) {
225                     if(!key)
226                     {
227                         error_set(error, line,
228                                 "Expected key, got identifier '%c'!", *fmt);
229                         root = NULL;
230                         goto out;
231                     }
232
233                     json_object_set_new(cur, key, obj);
234                     key = NULL;
235                 }
236                 else if(json_is_array(cur))
237                 {
238                     json_array_append_new(cur, obj);
239                 }
240                 else if(!root)
241                 {
242                     printf("Rooting\n");
243                     root = obj;
244                 }
245                 else
246                 {
247                     error_set(error, line, "Can't figure out where to attach "
248                               "'%c' object!", *fmt);
249                     root = NULL;
250                     goto out;
251                 }
252
253                 /* If it was a container ('[' or '{'), descend on the stack */
254                 if(json_is_array(obj) || json_is_object(obj))
255                 {
256                     stack[depth++] = cur;
257                     cur = obj;
258                 }
259
260                 break;
261         }
262         fmt++;
263     }
264     va_end(ap);
265
266     if(depth != 0) {
267         error_set(error, line,
268                 "Missing object or array close-brackets in format string");
269         root = NULL;
270         goto out;
271     }
272
273     /* Success: don't free everything we just built! */
274     free_count = 0;
275
276 out:
277     while(free_count)
278         json_decref(free_list[--free_count]);
279
280     if(free_list)
281         free(free_list);
282
283     if(stack)
284         free(stack);
285
286     return(root);
287 }
288
289 int json_unpack(json_t *root, json_error_t **error, const char *fmt, ...) {
290     va_list ap;
291
292     int rv=0; /* Return value */
293     int line = 1; /* Line number */
294
295     /* Keep a stack of containers (lists and objects) */
296     int depth = 0;
297     json_t **stack;
298
299     int array_index = 0;
300     char *key = NULL; /* Current key in an object */
301
302     json_t *cur = NULL; /* Current container */
303     json_t *obj = NULL;
304
305     int fmt_length = strlen(fmt);
306
307     error_init(error);
308
309     /* Allocation provisioned for worst case */
310     stack = calloc(fmt_length, sizeof(json_t *));
311     if(!stack)
312     {
313         error_set(error, line, "Out of memory!");
314         rv = -1;
315         goto out;
316     }
317
318     /* Even if we're successful, we need to know if the number of
319      * arguments provided matches the number of JSON objects.
320      * We can do this by counting the elements in every array or
321      * object we open up, and decrementing the count as we visit
322      * their children. */
323     int unvisited = 0;
324
325     va_start(ap, fmt);
326     while(*fmt)
327     {
328         switch(*fmt)
329         {
330             case ' ': /* Whitespace */
331                 break;
332
333             case '\n': /* Line break */
334                 line++;
335                 break;
336
337             case ',': /* Element spacer */
338
339                 if(!cur)
340                 {
341                     error_set(error, line,
342                               "Unexpected COMMA outside a list or object!");
343                     rv = -1;
344                     goto out;
345                 }
346
347                 if(key)
348                 {
349                     error_set(error, line, "Expected KEY, got COMMA!");
350                     rv = -1;
351                     goto out;
352                 }
353                 break;
354
355             case ':': /* Key/value separator */
356                 if(!json_is_object(cur) || !key)
357                 {
358                     error_set(error, line, "Unexpected ':'");
359                     rv = -1;
360                     goto out;
361                 }
362                 break;
363
364             case '[':
365             case '{':
366                 /* Fetch object */
367                 if(!cur)
368                 {
369                     obj = root;
370                 }
371                 else if(json_is_object(cur))
372                 {
373                     if(!key)
374                     {
375                         error_set(error, line, "Objects can't be keys");
376                         rv = -1;
377                         goto out;
378                     }
379                     obj = json_object_get(cur, key);
380                     unvisited--;
381                     key = NULL;
382                 }
383                 else if(json_is_array(cur))
384                 {
385                     obj = json_array_get(cur, array_index);
386                     unvisited--;
387                     array_index++;
388                 }
389                 else
390                 {
391                     assert(0);
392                 }
393
394                 /* Make sure we got what we expected */
395                 if(*fmt=='{' && !json_is_object(obj))
396                 {
397                     rv = -2;
398                     goto out;
399                 }
400
401                 if(*fmt=='[' && !json_is_array(obj))
402                 {
403                     rv = -2;
404                     goto out;
405                 }
406
407                 unvisited += json_is_object(obj) ?
408                     json_object_size(obj) :
409                     json_array_size(obj);
410
411                 /* Descend */
412                 stack[depth++] = cur;
413                 cur = obj;
414
415                 key = NULL;
416
417                 break;
418
419
420             case ']':
421             case '}':
422
423                 if(json_is_array(cur) && *fmt!=']')
424                 {
425                     error_set(error, line, "Missing ']'");
426                     rv = -1;
427                     goto out;
428                 }
429
430                 if(json_is_object(cur) && *fmt!='}')
431                 {
432                     error_set(error, line, "Missing '}'");
433                     rv = -1;
434                     goto out;
435                 }
436
437                 if(key)
438                 {
439                     error_set(error, line, "Unexpected '%c'", *fmt);
440                     rv = -1;
441                     goto out;
442                 }
443
444                 if(depth <= 0)
445                 {
446                     error_set(error, line, "Unexpected '%c'", *fmt);
447                     rv = -1;
448                     goto out;
449                 }
450
451                 cur = stack[--depth];
452
453                 break;
454
455             case 's':
456                 if(!key && json_is_object(cur))
457                 {
458                     /* constant string for key */
459                     key = va_arg(ap, char*);
460                     break;
461                 }
462                 /* fall through */
463
464             case 'i': /* integer */
465             case 'f': /* double-precision float */
466             case 'O': /* a json_t object; increments refcount */
467             case 'o': /* a json_t object; borrowed reference */
468             case 'b': /* boolean */
469             case 'n': /* null */
470
471                 /* Fetch object */
472                 if(!cur)
473                 {
474                     obj = root;
475                 }
476                 else if(json_is_object(cur))
477                 {
478                     if(!key)
479                     {
480                         error_set(error, line,
481                                   "Only strings may be used as keys!");
482                         rv = -1;
483                         goto out;
484                     }
485
486                     obj = json_object_get(cur, key);
487                     unvisited--;
488                     key = NULL;
489                 }
490                 else if(json_is_array(cur))
491                 {
492                     obj = json_array_get(cur, array_index);
493                     unvisited--;
494                     array_index++;
495                 }
496                 else
497                 {
498                     error_set(error, line,
499                               "Unsure how to retrieve JSON object '%c'",
500                               *fmt);
501                     rv = -1;
502                     goto out;
503                 }
504
505                 switch(*fmt)
506                 {
507                     case 's':
508                         if(!json_is_string(obj))
509                         {
510                             error_set(error, line,
511                                     "Type mismatch! Object wasn't a string.");
512                             rv = -2;
513                             goto out;
514                         }
515                         *va_arg(ap, const char**) = json_string_value(obj);
516                         break;
517
518                     case 'i':
519                         if(!json_is_integer(obj))
520                         {
521                             error_set(error, line,
522                                     "Type mismatch! Object wasn't an integer.");
523                             rv = -2;
524                             goto out;
525                         }
526                         *va_arg(ap, int*) = json_integer_value(obj);
527                         break;
528
529                     case 'b':
530                         if(!json_is_boolean(obj))
531                         {
532                             error_set(error, line,
533                                     "Type mismatch! Object wasn't a boolean.");
534                             rv = -2;
535                             goto out;
536                         }
537                         *va_arg(ap, int*) = json_is_true(obj);
538                         break;
539
540                     case 'f':
541                         if(!json_is_number(obj))
542                         {
543                             error_set(error, line,
544                                     "Type mismatch! Object wasn't a real.");
545                             rv = -2;
546                             goto out;
547                         }
548                         *va_arg(ap, double*) = json_number_value(obj);
549                         break;
550
551                     case 'O':
552                         json_incref(obj);
553                         /* Fall through */
554
555                     case 'o':
556                         *va_arg(ap, json_t**) = obj;
557                         break;
558
559                     case 'n':
560                         /* Don't actually assign anything; we're just happy
561                          * the null turned up as promised in the format
562                          * string. */
563                         break;
564
565                     default:
566                         error_set(error, line,
567                                 "Unknown format character '%c'", *fmt);
568                         rv = -1;
569                         goto out;
570                 }
571         }
572         fmt++;
573     }
574
575     /* Return 0 if everything was matched; otherwise the number of JSON
576      * objects we didn't get to. */
577     rv = unvisited;
578
579 out:
580     va_end(ap);
581
582     if(stack)
583         free(stack);
584
585     return(rv);
586 }
587
588 /* vim: ts=4:expandtab:sw=4
589  */