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