Home | History | Annotate | Line # | Download | only in libmj
      1 /*-
      2  * Copyright (c) 2010 Alistair Crooks <agc (at) NetBSD.org>
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 #include <sys/types.h>
     26 
     27 #include <inttypes.h>
     28 #include <regex.h>
     29 #include <stdarg.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <string.h>
     33 #include <unistd.h>
     34 
     35 #include "mj.h"
     36 #include "defs.h"
     37 
     38 #define JSON_ESCAPE '\xac'
     39 #define JSON_INDENT 4
     40 
     41 /*
     42  * save 'n' chars of 's' in malloc'd memory
     43  *
     44  * optionally encode embedded quotes and null bytes
     45  */
     46 static char *
     47 strnsave(const char *s, int n, int encoded)
     48 {
     49 	char	*newc;
     50 	char	*cp;
     51 	int	 i;
     52 
     53 	if (n < 0) {
     54 		n = (int)strlen(s);
     55 	}
     56 	NEWARRAY(char, cp, n + n + 1, "strnsave", return NULL);
     57 	switch (encoded) {
     58 	case MJ_JSON_ENCODE:
     59 		newc = cp;
     60 		for (i = 0 ; i < n ; i++) {
     61 			if (*s == JSON_ESCAPE) {
     62 				*newc++ = JSON_ESCAPE;
     63 				*newc++ = '1';
     64 				s += 1;
     65 			} else if (*s == '"') {
     66 				*newc++ = JSON_ESCAPE;
     67 				*newc++ = '2';
     68 				s += 1;
     69 			} else if (*s == 0x0) {
     70 				*newc++ = JSON_ESCAPE;
     71 				*newc++ = '0';
     72 				s += 1;
     73 			} else {
     74 				*newc++ = *s++;
     75 			}
     76 		}
     77 		*newc = 0x0;
     78 		break;
     79 	default:
     80 		(void) memcpy(cp, s, (unsigned)n);
     81 		cp[n] = 0x0;
     82 		break;
     83 	}
     84 	return cp;
     85 }
     86 
     87 /* look in an object for the item */
     88 static int
     89 findentry(mj_t *atom, const char *name, const unsigned from, const unsigned incr)
     90 {
     91 	unsigned	i;
     92 
     93 	for (i = from ; i < atom->c ; i += incr) {
     94 		if (strcmp(name, atom->value.v[i].value.s) == 0) {
     95 			return i;
     96 		}
     97 	}
     98 	return -1;
     99 }
    100 
    101 /* create a real number */
    102 static void
    103 create_number(mj_t *atom, double d)
    104 {
    105 	char	number[128];
    106 
    107 	atom->type = MJ_NUMBER;
    108 	atom->c = snprintf(number, sizeof(number), "%g", d);
    109 	atom->value.s = strnsave(number, (int)atom->c, MJ_HUMAN);
    110 }
    111 
    112 /* create an integer */
    113 static void
    114 create_integer(mj_t *atom, int64_t i)
    115 {
    116 	char	number[128];
    117 
    118 	atom->type = MJ_NUMBER;
    119 	atom->c = snprintf(number, sizeof(number), "%" PRIi64, i);
    120 	atom->value.s = strnsave(number, (int)atom->c, MJ_HUMAN);
    121 }
    122 
    123 /* create a string */
    124 static void
    125 create_string(mj_t *atom, const char *s, ssize_t len)
    126 {
    127 	atom->type = MJ_STRING;
    128 	atom->value.s = strnsave(s, (int)len, MJ_JSON_ENCODE);
    129 	atom->c = (unsigned)strlen(atom->value.s);
    130 }
    131 
    132 #define MJ_OPEN_BRACKET		(MJ_OBJECT + 1)		/* 8 */
    133 #define MJ_CLOSE_BRACKET	(MJ_OPEN_BRACKET + 1)	/* 9 */
    134 #define MJ_OPEN_BRACE		(MJ_CLOSE_BRACKET + 1)	/* 10 */
    135 #define MJ_CLOSE_BRACE		(MJ_OPEN_BRACE + 1)	/* 11 */
    136 #define MJ_COLON		(MJ_CLOSE_BRACE + 1)	/* 12 */
    137 #define MJ_COMMA		(MJ_COLON + 1)		/* 13 */
    138 
    139 /* return the token type, and start and finish locations in string */
    140 static int
    141 gettok(const char *s, int *from, int *to, int *tok)
    142 {
    143 	static regex_t	tokregex;
    144 	regmatch_t	matches[15];
    145 	static int	compiled;
    146 
    147 	if (!compiled) {
    148 		compiled = 1;
    149 		(void) regcomp(&tokregex,
    150 			"[ \t\r\n]*(([+-]?[0-9]{1,21}(\\.[0-9]*)?([eE][-+][0-9]+)?)|"
    151 			"(\"([^\"]|\\\\.)*\")|(null)|(false)|(true)|([][{}:,]))",
    152 			REG_EXTENDED);
    153 	}
    154 	if (regexec(&tokregex, &s[*from = *to], 15, matches, 0) != 0) {
    155 		return *tok = -1;
    156 	}
    157 	*to = *from + (int)(matches[1].rm_eo);
    158 	*tok = (matches[2].rm_so >= 0) ? MJ_NUMBER :
    159 		(matches[5].rm_so >= 0) ? MJ_STRING :
    160 		(matches[7].rm_so >= 0) ? MJ_NULL :
    161 		(matches[8].rm_so >= 0) ? MJ_FALSE :
    162 		(matches[9].rm_so >= 0) ? MJ_TRUE :
    163 		(matches[10].rm_so < 0) ? -1 :
    164 			(s[*from + (int)(matches[10].rm_so)] == '[') ? MJ_OPEN_BRACKET :
    165 			(s[*from + (int)(matches[10].rm_so)] == ']') ? MJ_CLOSE_BRACKET :
    166 			(s[*from + (int)(matches[10].rm_so)] == '{') ? MJ_OPEN_BRACE :
    167 			(s[*from + (int)(matches[10].rm_so)] == '}') ? MJ_CLOSE_BRACE :
    168 			(s[*from + (int)(matches[10].rm_so)] == ':') ? MJ_COLON :
    169 				MJ_COMMA;
    170 	*from += (int)(matches[1].rm_so);
    171 	return *tok;
    172 }
    173 
    174 /* minor function used to indent a JSON field */
    175 static void
    176 indent(FILE *fp, unsigned depth, const char *trailer)
    177 {
    178 	unsigned	i;
    179 
    180 	for (i = 0 ; i < depth ; i++) {
    181 		(void) fprintf(fp, " ");
    182 	}
    183 	if (trailer) {
    184 		(void) fprintf(fp, "%s", trailer);
    185 	}
    186 }
    187 
    188 /***************************************************************************/
    189 
    190 /* return the number of entries in the array */
    191 int
    192 mj_arraycount(mj_t *atom)
    193 {
    194 	return atom->c;
    195 }
    196 
    197 /* create a new JSON node */
    198 int
    199 mj_create(mj_t *atom, const char *type, ...)
    200 {
    201 	va_list	 args;
    202 	ssize_t	 len;
    203 	char	*s;
    204 
    205 	if (strcmp(type, "false") == 0) {
    206 		atom->type = MJ_FALSE;
    207 		atom->c = 0;
    208 	} else if (strcmp(type, "true") == 0) {
    209 		atom->type = MJ_TRUE;
    210 		atom->c = 1;
    211 	} else if (strcmp(type, "null") == 0) {
    212 		atom->type = MJ_NULL;
    213 	} else if (strcmp(type, "number") == 0) {
    214 		va_start(args, type);
    215 		create_number(atom, (double)va_arg(args, double));
    216 		va_end(args);
    217 	} else if (strcmp(type, "integer") == 0) {
    218 		va_start(args, type);
    219 		create_integer(atom, (int64_t)va_arg(args, int64_t));
    220 		va_end(args);
    221 	} else if (strcmp(type, "string") == 0) {
    222 		va_start(args, type);
    223 		s = (char *)va_arg(args, char *);
    224 		len = (size_t)va_arg(args, size_t);
    225 		va_end(args);
    226 		create_string(atom, s, len);
    227 	} else if (strcmp(type, "array") == 0) {
    228 		atom->type = MJ_ARRAY;
    229 	} else if (strcmp(type, "object") == 0) {
    230 		atom->type = MJ_OBJECT;
    231 	} else {
    232 		(void) fprintf(stderr, "weird type '%s'\n", type);
    233 		return 0;
    234 	}
    235 	return 1;
    236 }
    237 
    238 /*
    239  * put a JSON tree into a text string
    240  *
    241  * optionally keep encoded quotes and null bytes
    242  */
    243 int
    244 mj_snprint(char *buf, size_t size, mj_t *atom, int encoded)
    245 {
    246 	unsigned	 i;
    247 	char		*s;
    248 	char		*bp;
    249 	int		 cc;
    250 
    251 	switch(atom->type) {
    252 	case MJ_NULL:
    253 		return snprintf(buf, size, "null");
    254 	case MJ_FALSE:
    255 		return snprintf(buf, size, "false");
    256 	case MJ_TRUE:
    257 		return snprintf(buf, size, "true");
    258 	case MJ_NUMBER:
    259 		return snprintf(buf, size, "%s", atom->value.s);
    260 	case MJ_STRING:
    261 		if (size < 3)
    262 			return 0;
    263 		switch (encoded) {
    264 		case MJ_JSON_ENCODE:
    265 			return snprintf(buf, size, "\"%s\"", atom->value.s);
    266 		default:
    267 			for (bp = buf, *bp++ = '"', s = atom->value.s ;
    268 			     (size_t)(bp - buf) < size - 2 && (unsigned)(s - atom->value.s) < atom->c ; ) {
    269 				if (*s == JSON_ESCAPE) {
    270 					switch(s[1]) {
    271 					case '0':
    272 						if ((size_t)(bp - buf) < size - 3)
    273 							break;
    274 						*bp++ = '\\';
    275 						*bp++ = '0';
    276 						s += 2;
    277 						break;
    278 					case '1':
    279 						*bp++ = JSON_ESCAPE;
    280 						s += 2;
    281 						break;
    282 					case '2':
    283 						if ((size_t)(bp - buf) < size - 3)
    284 							break;
    285 						*bp++ = '\\';
    286 						*bp++ = '"';
    287 						s += 2;
    288 						break;
    289 					default:
    290 						(void) fprintf(stderr, "unrecognised character '%02x'\n", (uint8_t)s[1]);
    291 						s += 1;
    292 						break;
    293 					}
    294 				} else {
    295 					*bp++ = *s++;
    296 				}
    297 			}
    298 			*bp++ = '"';
    299 			*bp = 0x0;
    300 			return bp - buf;
    301 		}
    302 	case MJ_ARRAY:
    303 		cc = snprintf(buf, size, "[ ");
    304 		for (i = 0 ; i < atom->c ; i++) {
    305 			const char *sep = i+1 < atom->c ? ", " : " ";
    306 
    307 			cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i], encoded);
    308 			cc += snprintf(&buf[cc], size - cc, "%s", sep);
    309 		}
    310 		return cc + snprintf(&buf[cc], size - cc, "]\n");
    311 	case MJ_OBJECT:
    312 		cc = snprintf(buf, size, "{ ");
    313 		for (i = 0 ; i < atom->c - 1; i += 2) {
    314 			const char *sep = i+2 < atom->c ? ", " : " ";
    315 
    316 			cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i], encoded);
    317 			cc += snprintf(&buf[cc], size - cc, ":");
    318 			cc += mj_snprint(&buf[cc], size - cc, &atom->value.v[i + 1], encoded);
    319 			cc += snprintf(&buf[cc], size - cc, "%s", sep);
    320 		}
    321 		return cc + snprintf(&buf[cc], size - cc, "}\n");
    322 	default:
    323 		(void) fprintf(stderr, "mj_snprint: weird type %d\n", atom->type);
    324 		return 0;
    325 	}
    326 }
    327 
    328 /* allocate and print the atom */
    329 int
    330 mj_asprint(char **buf, mj_t *atom, int encoded)
    331 {
    332 	size_t	size;
    333 
    334 	size = mj_string_size(atom) + 1;
    335 	if ((*buf = calloc(1, size)) == NULL) {
    336 		return -1;
    337 	}
    338 	return mj_snprint(*buf, size, atom, encoded);
    339 }
    340 
    341 /* read into a JSON tree from a string */
    342 int
    343 mj_parse(mj_t *atom, const char *s, int *from, int *to, int *tok)
    344 {
    345 	int	i;
    346 
    347 	switch(atom->type = *tok = gettok(s, from, to, tok)) {
    348 	case MJ_NUMBER:
    349 		atom->value.s = strnsave(&s[*from], *to - *from, MJ_HUMAN);
    350 		atom->c = atom->size = (unsigned)strlen(atom->value.s);
    351 		return gettok(s, from, to, tok);
    352 	case MJ_STRING:
    353 		atom->value.s = strnsave(&s[*from + 1], *to - *from - 2, MJ_JSON_ENCODE);
    354 		atom->c = atom->size = (unsigned)strlen(atom->value.s);
    355 		return gettok(s, from, to, tok);
    356 	case MJ_NULL:
    357 	case MJ_FALSE:
    358 	case MJ_TRUE:
    359 		atom->c = (unsigned)*to;
    360 		return gettok(s, from, to, tok);
    361 	case MJ_OPEN_BRACKET:
    362 		mj_create(atom, "array");
    363 		ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
    364 		while (mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACKET) {
    365 			if (*tok != MJ_COMMA) {
    366 				(void) fprintf(stderr, "1. expected comma (got %d) at '%s'\n", *tok, &s[*from]);
    367 				break;
    368 			}
    369 			ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
    370 		}
    371 		return gettok(s, from, to, tok);
    372 	case MJ_OPEN_BRACE:
    373 		mj_create(atom, "object");
    374 		ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
    375 		for (i = 0 ; mj_parse(&atom->value.v[atom->c++], s, from, to, tok) >= 0 && *tok != MJ_CLOSE_BRACE ; i++) {
    376 			if (((i % 2) == 0 && *tok != MJ_COLON) || ((i % 2) == 1 && *tok != MJ_COMMA)) {
    377 				(void) fprintf(stderr, "2. expected comma (got %d) at '%s'\n", *tok, &s[*from]);
    378 				break;
    379 			}
    380 			ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_parse()", return 0);
    381 		}
    382 		return gettok(s, from, to, tok);
    383 	default:
    384 		return *tok;
    385 	}
    386 }
    387 
    388 /* return the index of the item which corresponds to the name in the array */
    389 int
    390 mj_object_find(mj_t *atom, const char *name, const unsigned from, const unsigned incr)
    391 {
    392 	return findentry(atom, name, from, incr);
    393 }
    394 
    395 /* find an atom in a composite mj JSON node */
    396 mj_t *
    397 mj_get_atom(mj_t *atom, ...)
    398 {
    399 	unsigned	 i;
    400 	va_list		 args;
    401 	char		*name;
    402 	int		 n;
    403 
    404 	switch(atom->type) {
    405 	case MJ_ARRAY:
    406 		va_start(args, atom);
    407 		i = va_arg(args, int);
    408 		va_end(args);
    409 		return (i < atom->c) ? &atom->value.v[i] : NULL;
    410 	case MJ_OBJECT:
    411 		va_start(args, atom);
    412 		name = va_arg(args, char *);
    413 		va_end(args);
    414 		return ((n = findentry(atom, name, 0, 2)) >= 0) ? &atom->value.v[n + 1] : NULL;
    415 	default:
    416 		return NULL;
    417 	}
    418 }
    419 
    420 /* perform a deep copy on an mj JSON atom */
    421 int
    422 mj_deepcopy(mj_t *dst, mj_t *src)
    423 {
    424 	unsigned	i;
    425 
    426 	switch(src->type) {
    427 	case MJ_FALSE:
    428 	case MJ_TRUE:
    429 	case MJ_NULL:
    430 		(void) memcpy(dst, src, sizeof(*dst));
    431 		return 1;
    432 	case MJ_STRING:
    433 	case MJ_NUMBER:
    434 		(void) memcpy(dst, src, sizeof(*dst));
    435 		dst->value.s = strnsave(src->value.s, -1, MJ_HUMAN);
    436 		dst->c = dst->size = (unsigned)strlen(dst->value.s);
    437 		return 1;
    438 	case MJ_ARRAY:
    439 	case MJ_OBJECT:
    440 		(void) memcpy(dst, src, sizeof(*dst));
    441 		NEWARRAY(mj_t, dst->value.v, dst->size, "mj_deepcopy()", return 0);
    442 		for (i = 0 ; i < src->c ; i++) {
    443 			if (!mj_deepcopy(&dst->value.v[i], &src->value.v[i])) {
    444 				return 0;
    445 			}
    446 		}
    447 		return 1;
    448 	default:
    449 		(void) fprintf(stderr, "weird type '%d'\n", src->type);
    450 		return 0;
    451 	}
    452 }
    453 
    454 /* do a deep delete on the object */
    455 void
    456 mj_delete(mj_t *atom)
    457 {
    458 	unsigned	i;
    459 
    460 	switch(atom->type) {
    461 	case MJ_STRING:
    462 	case MJ_NUMBER:
    463 		free(atom->value.s);
    464 		break;
    465 	case MJ_ARRAY:
    466 	case MJ_OBJECT:
    467 		for (i = 0 ; i < atom->c ; i++) {
    468 			mj_delete(&atom->value.v[i]);
    469 		}
    470 		/* XXX - agc - causing problems? free(atom->value.v); */
    471 		break;
    472 	default:
    473 		break;
    474 	}
    475 }
    476 
    477 /* return the string size needed for the textual output of the JSON node */
    478 int
    479 mj_string_size(mj_t *atom)
    480 {
    481 	unsigned	i;
    482 	int		cc;
    483 
    484 	switch(atom->type) {
    485 	case MJ_NULL:
    486 	case MJ_TRUE:
    487 		/* true */
    488 		return 4;
    489 	case MJ_FALSE:
    490 		/* false */
    491 		return 5;
    492 	case MJ_NUMBER:
    493 		return atom->c;
    494 	case MJ_STRING:
    495 		/* "string" */
    496 		return atom->c + 2;
    497 	case MJ_ARRAY:
    498 		/* start '[ ' */
    499 		for (cc = 2, i = 0 ; i < atom->c ; i++) {
    500 			cc += mj_string_size(&atom->value.v[i]);
    501 			/* separator ', ' or ' ' */
    502 			cc += (i < atom->c - 1) ? 2 : 1;
    503 		}
    504 		/* end ']' */
    505 		return cc + 1;
    506 	case MJ_OBJECT:
    507 		/* start '{ ' */
    508 		for (cc = 2, i = 0 ; i < atom->c ; i += 2) {
    509 			/* key:value */
    510 			cc += mj_string_size(&atom->value.v[i]) + 1 + mj_string_size(&atom->value.v[i + 1]);
    511 			/* separator ', ' or ' ' */
    512 			cc += (i < atom->c - 1) ? 2 : 1;
    513 		}
    514 		/* end '}' */
    515 		return cc + 1;
    516 	default:
    517 		(void) fprintf(stderr, "mj_string_size: weird type %d\n", atom->type);
    518 		return 0;
    519 	}
    520 }
    521 
    522 /* create a new atom, and append it to the array or object */
    523 int
    524 mj_append(mj_t *atom, const char *type, ...)
    525 {
    526 	va_list	 args;
    527 	ssize_t	 len;
    528 	char	*s;
    529 
    530 	if (atom->type != MJ_ARRAY && atom->type != MJ_OBJECT) {
    531 		return 0;
    532 	}
    533 	ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append()", return 0);
    534 	va_start(args, type);
    535 	if (strcmp(type, "string") == 0) {
    536 		s = (char *)va_arg(args, char *);
    537 		len = (ssize_t)va_arg(args, ssize_t);
    538 		create_string(&atom->value.v[atom->c++], s, len);
    539 	} else if (strcmp(type, "integer") == 0) {
    540 		create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t));
    541 	} else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) {
    542 		mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *));
    543 	} else {
    544 		(void) fprintf(stderr, "mj_append: weird type '%s'\n", type);
    545 	}
    546 	va_end(args);
    547 	return 1;
    548 }
    549 
    550 /* append a field to an object */
    551 int
    552 mj_append_field(mj_t *atom, const char *name, const char *type, ...)
    553 {
    554 	va_list	 args;
    555 	ssize_t	 len;
    556 	char	*s;
    557 
    558 	if (atom->type != MJ_OBJECT) {
    559 		return 0;
    560 	}
    561 	mj_append(atom, "string", name, -1);
    562 	ALLOC(mj_t, atom->value.v, atom->size, atom->c, 10, 10, "mj_append_field()", return 0);
    563 	va_start(args, type);
    564 	if (strcmp(type, "string") == 0) {
    565 		s = (char *)va_arg(args, char *);
    566 		len = (ssize_t)va_arg(args, ssize_t);
    567 		create_string(&atom->value.v[atom->c++], s, len);
    568 	} else if (strcmp(type, "integer") == 0) {
    569 		create_integer(&atom->value.v[atom->c++], (int64_t)va_arg(args, int64_t));
    570 	} else if (strcmp(type, "object") == 0 || strcmp(type, "array") == 0) {
    571 		mj_deepcopy(&atom->value.v[atom->c++], (mj_t *)va_arg(args, mj_t *));
    572 	} else {
    573 		(void) fprintf(stderr, "mj_append_field: weird type '%s'\n", type);
    574 	}
    575 	va_end(args);
    576 	return 1;
    577 }
    578 
    579 /* make sure a JSON object is politically correct */
    580 int
    581 mj_lint(mj_t *obj)
    582 {
    583 	unsigned	i;
    584 	int		ret;
    585 
    586 	switch(obj->type) {
    587 	case MJ_NULL:
    588 	case MJ_FALSE:
    589 	case MJ_TRUE:
    590 		if (obj->value.s != NULL) {
    591 			(void) fprintf(stderr, "null/false/true: non zero string\n");
    592 			return 0;
    593 		}
    594 		return 1;
    595 	case MJ_NUMBER:
    596 	case MJ_STRING:
    597 		if (obj->c > obj->size) {
    598 			(void) fprintf(stderr, "string/number lint c (%u) > size (%u)\n", obj->c, obj->size);
    599 			return 0;
    600 		}
    601 		return 1;
    602 	case MJ_ARRAY:
    603 	case MJ_OBJECT:
    604 		if (obj->c > obj->size) {
    605 			(void) fprintf(stderr, "array/object lint c (%u) > size (%u)\n", obj->c, obj->size);
    606 			return 0;
    607 		}
    608 		for (ret = 1, i = 0 ; i < obj->c ; i++) {
    609 			if (!mj_lint(&obj->value.v[i])) {
    610 				(void) fprintf(stderr, "array/object lint found at %d of %p\n", i, obj);
    611 				ret = 0;
    612 			}
    613 		}
    614 		return ret;
    615 	default:
    616 		(void) fprintf(stderr, "problem type %d in %p\n", obj->type, obj);
    617 		return 0;
    618 	}
    619 }
    620 
    621 /* pretty-print a JSON struct - can be called recursively */
    622 int
    623 mj_pretty(mj_t *mj, void *vp, unsigned depth, const char *trailer)
    624 {
    625 	unsigned	 i;
    626 	FILE		*fp;
    627 	char		*s;
    628 
    629 	fp = (FILE *)vp;
    630 	switch(mj->type) {
    631 	case MJ_NUMBER:
    632 	case MJ_TRUE:
    633 	case MJ_FALSE:
    634 	case MJ_NULL:
    635 		indent(fp, depth, mj->value.s);
    636 		break;
    637 	case MJ_STRING:
    638 		indent(fp, depth, NULL);
    639 		mj_asprint(&s, mj, MJ_HUMAN);
    640 		(void) fprintf(fp, "%s", s);
    641 		free(s);
    642 		break;
    643 	case MJ_ARRAY:
    644 		indent(fp, depth, "[\n");
    645 		for (i = 0 ; i < mj->c ; i++) {
    646 			mj_pretty(&mj->value.v[i], fp, depth + JSON_INDENT, (i < mj->c - 1) ? ",\n" : "\n");
    647 		}
    648 		indent(fp, depth, "]");
    649 		break;
    650 	case MJ_OBJECT:
    651 		indent(fp, depth, "{\n");
    652 		for (i = 0 ; i < mj->c ; i += 2) {
    653 			mj_pretty(&mj->value.v[i], fp, depth + JSON_INDENT, " : ");
    654 			mj_pretty(&mj->value.v[i + 1], fp, 0, (i < mj->c - 2) ? ",\n" : "\n");
    655 		}
    656 		indent(fp, depth, "}");
    657 		break;
    658 	}
    659 	indent(fp, 0, trailer);
    660 	return 1;
    661 }
    662 
    663 /* show the contents of the simple atom as a string representation */
    664 const char *
    665 mj_string_rep(mj_t *atom)
    666 {
    667 	if (atom == NULL) {
    668 		return 0;
    669 	}
    670 	switch(atom->type) {
    671 	case MJ_STRING:
    672 	case MJ_NUMBER:
    673 		return atom->value.s;
    674 	case MJ_NULL:
    675 		return "null";
    676 	case MJ_FALSE:
    677 		return "false";
    678 	case MJ_TRUE:
    679 		return "true";
    680 	default:
    681 		return NULL;
    682 	}
    683 }
    684