Escaping the slash when dump
[jansson.git] / src / dump.c
1 /*
2  * Copyright (c) 2009-2012 Petri Lehtinen <petri@digip.org>
3  *
4  * Jansson is free software; you can redistribute it and/or modify
5  * it under the terms of the MIT license. See LICENSE for details.
6  */
7
8 #define _GNU_SOURCE
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <assert.h>
13
14 #include <jansson.h>
15 #include "jansson_private.h"
16 #include "strbuffer.h"
17 #include "utf.h"
18
19 #define MAX_INTEGER_STR_LENGTH  100
20 #define MAX_REAL_STR_LENGTH     100
21
22 struct object_key {
23     size_t serial;
24     const char *key;
25 };
26
27 static int dump_to_strbuffer(const char *buffer, size_t size, void *data)
28 {
29     return strbuffer_append_bytes((strbuffer_t *)data, buffer, size);
30 }
31
32 static int dump_to_file(const char *buffer, size_t size, void *data)
33 {
34     FILE *dest = (FILE *)data;
35     if(fwrite(buffer, size, 1, dest) != 1)
36         return -1;
37     return 0;
38 }
39
40 /* 32 spaces (the maximum indentation size) */
41 static char whitespace[] = "                                ";
42
43 static int dump_indent(size_t flags, int depth, int space, json_dump_callback_t dump, void *data)
44 {
45     if(JSON_INDENT(flags) > 0)
46     {
47         int i, ws_count = JSON_INDENT(flags);
48
49         if(dump("\n", 1, data))
50             return -1;
51
52         for(i = 0; i < depth; i++)
53         {
54             if(dump(whitespace, ws_count, data))
55                 return -1;
56         }
57     }
58     else if(space && !(flags & JSON_COMPACT))
59     {
60         return dump(" ", 1, data);
61     }
62     return 0;
63 }
64
65 static int dump_string(const char *str, int ascii, json_dump_callback_t dump, void *data)
66 {
67     const char *pos, *end;
68     int32_t codepoint;
69
70     if(dump("\"", 1, data))
71         return -1;
72
73     end = pos = str;
74     while(1)
75     {
76         const char *text;
77         char seq[13];
78         int length;
79
80         while(*end)
81         {
82             end = utf8_iterate(pos, &codepoint);
83             if(!end)
84                 return -1;
85
86             /* mandatory escape or control char */
87             if(codepoint == '\\' || codepoint == '/' || codepoint == '"' || codepoint < 0x20)
88                 break;
89
90             /* non-ASCII */
91             if(ascii && codepoint > 0x7F)
92                 break;
93
94             pos = end;
95         }
96
97         if(pos != str) {
98             if(dump(str, pos - str, data))
99                 return -1;
100         }
101
102         if(end == pos)
103             break;
104
105         /* handle \, /, ", and control codes */
106         length = 2;
107         switch(codepoint)
108         {
109             case '\\': text = "\\\\"; break;
110             case '/': text = "\\/"; break;
111             case '\"': text = "\\\""; break;
112             case '\b': text = "\\b"; break;
113             case '\f': text = "\\f"; break;
114             case '\n': text = "\\n"; break;
115             case '\r': text = "\\r"; break;
116             case '\t': text = "\\t"; break;
117             default:
118             {
119                 /* codepoint is in BMP */
120                 if(codepoint < 0x10000)
121                 {
122                     sprintf(seq, "\\u%04x", codepoint);
123                     length = 6;
124                 }
125
126                 /* not in BMP -> construct a UTF-16 surrogate pair */
127                 else
128                 {
129                     int32_t first, last;
130
131                     codepoint -= 0x10000;
132                     first = 0xD800 | ((codepoint & 0xffc00) >> 10);
133                     last = 0xDC00 | (codepoint & 0x003ff);
134
135                     sprintf(seq, "\\u%04x\\u%04x", first, last);
136                     length = 12;
137                 }
138
139                 text = seq;
140                 break;
141             }
142         }
143
144         if(dump(text, length, data))
145             return -1;
146
147         str = pos = end;
148     }
149
150     return dump("\"", 1, data);
151 }
152
153 static int object_key_compare_keys(const void *key1, const void *key2)
154 {
155     return strcmp(((const struct object_key *)key1)->key,
156                   ((const struct object_key *)key2)->key);
157 }
158
159 static int object_key_compare_serials(const void *key1, const void *key2)
160 {
161     size_t a = ((const struct object_key *)key1)->serial;
162     size_t b = ((const struct object_key *)key2)->serial;
163
164     return a < b ? -1 : a == b ? 0 : 1;
165 }
166
167 static int do_dump(const json_t *json, size_t flags, int depth,
168                    json_dump_callback_t dump, void *data)
169 {
170     int ascii = flags & JSON_ENSURE_ASCII ? 1 : 0;
171
172     switch(json_typeof(json)) {
173         case JSON_NULL:
174             return dump("null", 4, data);
175
176         case JSON_TRUE:
177             return dump("true", 4, data);
178
179         case JSON_FALSE:
180             return dump("false", 5, data);
181
182         case JSON_INTEGER:
183         {
184             char buffer[MAX_INTEGER_STR_LENGTH];
185             int size;
186
187             size = snprintf(buffer, MAX_INTEGER_STR_LENGTH,
188                             "%" JSON_INTEGER_FORMAT,
189                             json_integer_value(json));
190             if(size >= MAX_INTEGER_STR_LENGTH)
191                 return -1;
192
193             return dump(buffer, size, data);
194         }
195
196         case JSON_REAL:
197         {
198             char buffer[MAX_REAL_STR_LENGTH];
199             int size;
200             double value = json_real_value(json);
201
202             size = jsonp_dtostr(buffer, MAX_REAL_STR_LENGTH, value);
203             if(size < 0)
204                 return -1;
205
206             return dump(buffer, size, data);
207         }
208
209         case JSON_STRING:
210             return dump_string(json_string_value(json), ascii, dump, data);
211
212         case JSON_ARRAY:
213         {
214             int i;
215             int n;
216             json_array_t *array;
217
218             /* detect circular references */
219             array = json_to_array(json);
220             if(array->visited)
221                 goto array_error;
222             array->visited = 1;
223
224             n = json_array_size(json);
225
226             if(dump("[", 1, data))
227                 goto array_error;
228             if(n == 0) {
229                 array->visited = 0;
230                 return dump("]", 1, data);
231             }
232             if(dump_indent(flags, depth + 1, 0, dump, data))
233                 goto array_error;
234
235             for(i = 0; i < n; ++i) {
236                 if(do_dump(json_array_get(json, i), flags, depth + 1,
237                            dump, data))
238                     goto array_error;
239
240                 if(i < n - 1)
241                 {
242                     if(dump(",", 1, data) ||
243                        dump_indent(flags, depth + 1, 1, dump, data))
244                         goto array_error;
245                 }
246                 else
247                 {
248                     if(dump_indent(flags, depth, 0, dump, data))
249                         goto array_error;
250                 }
251             }
252
253             array->visited = 0;
254             return dump("]", 1, data);
255
256         array_error:
257             array->visited = 0;
258             return -1;
259         }
260
261         case JSON_OBJECT:
262         {
263             json_object_t *object;
264             void *iter;
265             const char *separator;
266             int separator_length;
267
268             if(flags & JSON_COMPACT) {
269                 separator = ":";
270                 separator_length = 1;
271             }
272             else {
273                 separator = ": ";
274                 separator_length = 2;
275             }
276
277             /* detect circular references */
278             object = json_to_object(json);
279             if(object->visited)
280                 goto object_error;
281             object->visited = 1;
282
283             iter = json_object_iter((json_t *)json);
284
285             if(dump("{", 1, data))
286                 goto object_error;
287             if(!iter) {
288                 object->visited = 0;
289                 return dump("}", 1, data);
290             }
291             if(dump_indent(flags, depth + 1, 0, dump, data))
292                 goto object_error;
293
294             if(flags & JSON_SORT_KEYS || flags & JSON_PRESERVE_ORDER)
295             {
296                 struct object_key *keys;
297                 size_t size, i;
298                 int (*cmp_func)(const void *, const void *);
299
300                 size = json_object_size(json);
301                 keys = jsonp_malloc(size * sizeof(struct object_key));
302                 if(!keys)
303                     goto object_error;
304
305                 i = 0;
306                 while(iter)
307                 {
308                     keys[i].serial = hashtable_iter_serial(iter);
309                     keys[i].key = json_object_iter_key(iter);
310                     iter = json_object_iter_next((json_t *)json, iter);
311                     i++;
312                 }
313                 assert(i == size);
314
315                 if(flags & JSON_SORT_KEYS)
316                     cmp_func = object_key_compare_keys;
317                 else
318                     cmp_func = object_key_compare_serials;
319
320                 qsort(keys, size, sizeof(struct object_key), cmp_func);
321
322                 for(i = 0; i < size; i++)
323                 {
324                     const char *key;
325                     json_t *value;
326
327                     key = keys[i].key;
328                     value = json_object_get(json, key);
329                     assert(value);
330
331                     dump_string(key, ascii, dump, data);
332                     if(dump(separator, separator_length, data) ||
333                        do_dump(value, flags, depth + 1, dump, data))
334                     {
335                         jsonp_free(keys);
336                         goto object_error;
337                     }
338
339                     if(i < size - 1)
340                     {
341                         if(dump(",", 1, data) ||
342                            dump_indent(flags, depth + 1, 1, dump, data))
343                         {
344                             jsonp_free(keys);
345                             goto object_error;
346                         }
347                     }
348                     else
349                     {
350                         if(dump_indent(flags, depth, 0, dump, data))
351                         {
352                             jsonp_free(keys);
353                             goto object_error;
354                         }
355                     }
356                 }
357
358                 jsonp_free(keys);
359             }
360             else
361             {
362                 /* Don't sort keys */
363
364                 while(iter)
365                 {
366                     void *next = json_object_iter_next((json_t *)json, iter);
367
368                     dump_string(json_object_iter_key(iter), ascii, dump, data);
369                     if(dump(separator, separator_length, data) ||
370                        do_dump(json_object_iter_value(iter), flags, depth + 1,
371                                dump, data))
372                         goto object_error;
373
374                     if(next)
375                     {
376                         if(dump(",", 1, data) ||
377                            dump_indent(flags, depth + 1, 1, dump, data))
378                             goto object_error;
379                     }
380                     else
381                     {
382                         if(dump_indent(flags, depth, 0, dump, data))
383                             goto object_error;
384                     }
385
386                     iter = next;
387                 }
388             }
389
390             object->visited = 0;
391             return dump("}", 1, data);
392
393         object_error:
394             object->visited = 0;
395             return -1;
396         }
397
398         default:
399             /* not reached */
400             return -1;
401     }
402 }
403
404 char *json_dumps(const json_t *json, size_t flags)
405 {
406     strbuffer_t strbuff;
407     char *result;
408
409     if(strbuffer_init(&strbuff))
410         return NULL;
411
412     if(json_dump_callback(json, dump_to_strbuffer, (void *)&strbuff, flags))
413         result = NULL;
414     else
415         result = jsonp_strdup(strbuffer_value(&strbuff));
416
417     strbuffer_close(&strbuff);
418     return result;
419 }
420
421 int json_dumpf(const json_t *json, FILE *output, size_t flags)
422 {
423     return json_dump_callback(json, dump_to_file, (void *)output, flags);
424 }
425
426 int json_dump_file(const json_t *json, const char *path, size_t flags)
427 {
428     int result;
429
430     FILE *output = fopen(path, "w");
431     if(!output)
432         return -1;
433
434     result = json_dumpf(json, output, flags);
435
436     fclose(output);
437     return result;
438 }
439
440 int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags)
441 {
442     if(!(flags & JSON_ENCODE_ANY)) {
443         if(!json_is_array(json) && !json_is_object(json))
444            return -1;
445     }
446
447     return do_dump(json, flags, 0, callback, data);
448 }