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