mirror of
https://github.com/syoyo/tinygltf.git
synced 2026-06-08 03:03:50 +00:00
1908 lines
68 KiB
C++
1908 lines
68 KiB
C++
/*
|
|
* tinygltf_json.h - Fast JSON parser for tinygltf
|
|
*
|
|
* The MIT License (MIT)
|
|
* Copyright (c) 2015 - Present Syoyo Fujita, Aurelien Chatelain and many
|
|
* contributors.
|
|
*
|
|
* A custom JSON parser optimized for glTF processing.
|
|
*
|
|
* Design goals:
|
|
* - C-style implementation core (structs, raw pointers, malloc/free)
|
|
* - Minimal C++ wrappers for tinygltf interface compatibility
|
|
* - SIMD-accelerated whitespace skipping and string scanning
|
|
* - Flat storage arrays for cache-friendly memory layout
|
|
*
|
|
* SIMD activation (default: SIMD disabled):
|
|
* Define TINYGLTF_JSON_USE_SIMD to auto-detect CPU SIMD support, OR
|
|
* define one or more of the following explicitly:
|
|
* TINYGLTF_JSON_SIMD_SSE2 - Enable SSE2 (x86/x86-64)
|
|
* TINYGLTF_JSON_SIMD_AVX2 - Enable AVX2 (x86-64, implies SSE2)
|
|
* TINYGLTF_JSON_SIMD_NEON - Enable ARM NEON
|
|
*
|
|
* Exception handling (default: exceptions disabled):
|
|
* By default, parse errors silently return a null value.
|
|
* Define TINYGLTF_JSON_USE_EXCEPTIONS before including this header to
|
|
* allow tinygltf_json::parse() to throw std::invalid_argument on error
|
|
* when its allow_exceptions parameter is true.
|
|
*/
|
|
|
|
#ifndef TINYGLTF_JSON_H_
|
|
#define TINYGLTF_JSON_H_
|
|
|
|
/* C standard headers (keep these first for C compatibility) */
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
/* C++ headers (minimal) */
|
|
#include <string>
|
|
#include <cstddef> /* for std::nullptr_t */
|
|
#include <new> /* for placement-new */
|
|
/* Exception opt-in: define TINYGLTF_JSON_USE_EXCEPTIONS to enable throws.
|
|
* TINYGLTF_JSON_NO_EXCEPTIONS is the internal guard derived from the absence
|
|
* of TINYGLTF_JSON_USE_EXCEPTIONS; users should not define it directly. */
|
|
#ifndef TINYGLTF_JSON_USE_EXCEPTIONS
|
|
# define TINYGLTF_JSON_NO_EXCEPTIONS
|
|
#endif
|
|
#ifndef TINYGLTF_JSON_NO_EXCEPTIONS
|
|
# include <stdexcept>
|
|
#endif
|
|
|
|
/* ======================================================================
|
|
* SIMD detection
|
|
* ====================================================================== */
|
|
|
|
#ifdef TINYGLTF_JSON_USE_SIMD
|
|
# if defined(__AVX2__)
|
|
# define TINYGLTF_JSON_SIMD_AVX2
|
|
# endif
|
|
# if defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || \
|
|
(defined(_M_IX86_FP) && _M_IX86_FP >= 2)
|
|
# define TINYGLTF_JSON_SIMD_SSE2
|
|
# endif
|
|
# if defined(__ARM_NEON) || defined(__ARM_NEON__)
|
|
# define TINYGLTF_JSON_SIMD_NEON
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef TINYGLTF_JSON_SIMD_AVX2
|
|
# include <immintrin.h>
|
|
#elif defined(TINYGLTF_JSON_SIMD_SSE2)
|
|
# include <emmintrin.h>
|
|
#endif
|
|
|
|
#ifdef TINYGLTF_JSON_SIMD_NEON
|
|
# include <arm_neon.h>
|
|
#endif
|
|
|
|
/* ======================================================================
|
|
* JSON VALUE TYPE CONSTANTS (C-style integer constants)
|
|
* ====================================================================== */
|
|
|
|
#define CJ_NULL 0
|
|
#define CJ_BOOL 1
|
|
#define CJ_INT 2
|
|
#define CJ_REAL 3
|
|
#define CJ_STRING 4
|
|
#define CJ_ARRAY 5
|
|
#define CJ_OBJECT 6
|
|
|
|
/* ======================================================================
|
|
* SIMD WHITESPACE SKIPPING
|
|
*
|
|
* Whitespace characters in JSON: space(0x20), tab(0x09), CR(0x0D), LF(0x0A)
|
|
* ====================================================================== */
|
|
|
|
static const char *cj_skip_ws_scalar(const char *p, const char *end) {
|
|
while (p < end) {
|
|
unsigned char c = (unsigned char)*p;
|
|
if (c != 0x20u && c != 0x09u && c != 0x0Du && c != 0x0Au) break;
|
|
++p;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
#if defined(TINYGLTF_JSON_SIMD_AVX2)
|
|
|
|
static const char *cj_skip_ws(const char *p, const char *end) {
|
|
while (p + 32 <= end) {
|
|
__m256i chunk = _mm256_loadu_si256((const __m256i *)(const void *)p);
|
|
__m256i sp = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8(' '));
|
|
__m256i tab = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\t'));
|
|
__m256i cr = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\r'));
|
|
__m256i lf = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\n'));
|
|
__m256i ws = _mm256_or_si256(_mm256_or_si256(sp, tab),
|
|
_mm256_or_si256(cr, lf));
|
|
unsigned int mask = (unsigned int)_mm256_movemask_epi8(ws);
|
|
if (mask != 0xFFFFFFFFu) {
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
return p + (int)__builtin_ctz(~mask);
|
|
#else
|
|
unsigned int inv = ~mask, idx = 0;
|
|
while (!(inv & (1u << idx))) ++idx;
|
|
return p + idx;
|
|
#endif
|
|
}
|
|
p += 32;
|
|
}
|
|
return cj_skip_ws_scalar(p, end);
|
|
}
|
|
|
|
#elif defined(TINYGLTF_JSON_SIMD_SSE2)
|
|
|
|
static const char *cj_skip_ws(const char *p, const char *end) {
|
|
while (p + 16 <= end) {
|
|
__m128i chunk = _mm_loadu_si128((const __m128i *)(const void *)p);
|
|
__m128i sp = _mm_cmpeq_epi8(chunk, _mm_set1_epi8(' '));
|
|
__m128i tab = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\t'));
|
|
__m128i cr = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\r'));
|
|
__m128i lf = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\n'));
|
|
__m128i ws = _mm_or_si128(_mm_or_si128(sp, tab),
|
|
_mm_or_si128(cr, lf));
|
|
unsigned int mask = (unsigned int)_mm_movemask_epi8(ws);
|
|
if (mask != 0xFFFFu) {
|
|
unsigned int inv = (~mask) & 0xFFFFu;
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
return p + (int)__builtin_ctz(inv);
|
|
#else
|
|
unsigned int idx = 0;
|
|
while (!(inv & (1u << idx))) ++idx;
|
|
return p + idx;
|
|
#endif
|
|
}
|
|
p += 16;
|
|
}
|
|
return cj_skip_ws_scalar(p, end);
|
|
}
|
|
|
|
#elif defined(TINYGLTF_JSON_SIMD_NEON)
|
|
|
|
static const char *cj_skip_ws(const char *p, const char *end) {
|
|
while (p + 16 <= end) {
|
|
uint8x16_t chunk = vld1q_u8((const uint8_t *)p);
|
|
uint8x16_t sp = vceqq_u8(chunk, vdupq_n_u8(' '));
|
|
uint8x16_t tab = vceqq_u8(chunk, vdupq_n_u8('\t'));
|
|
uint8x16_t cr = vceqq_u8(chunk, vdupq_n_u8('\r'));
|
|
uint8x16_t lf = vceqq_u8(chunk, vdupq_n_u8('\n'));
|
|
uint8x16_t ws = vorrq_u8(vorrq_u8(sp, tab), vorrq_u8(cr, lf));
|
|
uint64x2_t ws64 = vreinterpretq_u64_u8(ws);
|
|
uint64_t lo = vgetq_lane_u64(ws64, 0);
|
|
uint64_t hi = vgetq_lane_u64(ws64, 1);
|
|
if (lo != UINT64_C(0xFFFFFFFFFFFFFFFF) ||
|
|
hi != UINT64_C(0xFFFFFFFFFFFFFFFF)) {
|
|
uint8_t tmp[16];
|
|
vst1q_u8(tmp, ws);
|
|
for (int i = 0; i < 16; ++i) {
|
|
if (!tmp[i]) return p + i;
|
|
}
|
|
}
|
|
p += 16;
|
|
}
|
|
return cj_skip_ws_scalar(p, end);
|
|
}
|
|
|
|
#else
|
|
|
|
static const char *cj_skip_ws(const char *p, const char *end) {
|
|
return cj_skip_ws_scalar(p, end);
|
|
}
|
|
|
|
#endif /* SIMD whitespace */
|
|
|
|
/* ======================================================================
|
|
* SIMD STRING SCANNING (find '"', '\', or control char)
|
|
* ====================================================================== */
|
|
|
|
static const char *cj_scan_str_scalar(const char *p, const char *end) {
|
|
while (p < end) {
|
|
unsigned char c = (unsigned char)*p;
|
|
if (c == '"' || c == '\\' || c < 0x20u) break;
|
|
++p;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
#if defined(TINYGLTF_JSON_SIMD_AVX2)
|
|
|
|
static const char *cj_scan_str(const char *p, const char *end) {
|
|
while (p + 32 <= end) {
|
|
__m256i chunk = _mm256_loadu_si256((const __m256i *)(const void *)p);
|
|
__m256i eq_q = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('"'));
|
|
__m256i eq_bs = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\\'));
|
|
/* Control chars: byte <= 0x1F <=> min(byte, 0x1F) == byte */
|
|
__m256i ctrl = _mm256_cmpeq_epi8(
|
|
_mm256_min_epu8(chunk, _mm256_set1_epi8(0x1F)),
|
|
chunk);
|
|
__m256i special = _mm256_or_si256(_mm256_or_si256(eq_q, eq_bs), ctrl);
|
|
unsigned int mask = (unsigned int)_mm256_movemask_epi8(special);
|
|
if (mask) {
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
return p + (int)__builtin_ctz(mask);
|
|
#else
|
|
unsigned int idx = 0;
|
|
while (!(mask & (1u << idx))) ++idx;
|
|
return p + idx;
|
|
#endif
|
|
}
|
|
p += 32;
|
|
}
|
|
return cj_scan_str_scalar(p, end);
|
|
}
|
|
|
|
#elif defined(TINYGLTF_JSON_SIMD_SSE2)
|
|
|
|
static const char *cj_scan_str(const char *p, const char *end) {
|
|
while (p + 16 <= end) {
|
|
__m128i chunk = _mm_loadu_si128((const __m128i *)(const void *)p);
|
|
__m128i eq_q = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('"'));
|
|
__m128i eq_bs = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\'));
|
|
__m128i ctrl = _mm_cmpeq_epi8(
|
|
_mm_min_epu8(chunk, _mm_set1_epi8(0x1F)),
|
|
chunk);
|
|
__m128i special = _mm_or_si128(_mm_or_si128(eq_q, eq_bs), ctrl);
|
|
unsigned int mask = (unsigned int)_mm_movemask_epi8(special);
|
|
if (mask) {
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
return p + (int)__builtin_ctz(mask);
|
|
#else
|
|
unsigned int idx = 0;
|
|
while (!(mask & (1u << idx))) ++idx;
|
|
return p + idx;
|
|
#endif
|
|
}
|
|
p += 16;
|
|
}
|
|
return cj_scan_str_scalar(p, end);
|
|
}
|
|
|
|
#elif defined(TINYGLTF_JSON_SIMD_NEON)
|
|
|
|
static const char *cj_scan_str(const char *p, const char *end) {
|
|
uint8x16_t vquote = vdupq_n_u8('"');
|
|
uint8x16_t vbslash = vdupq_n_u8('\\');
|
|
uint8x16_t v20 = vdupq_n_u8(0x20u);
|
|
while (p + 16 <= end) {
|
|
uint8x16_t chunk = vld1q_u8((const uint8_t *)p);
|
|
uint8x16_t eq_q = vceqq_u8(chunk, vquote);
|
|
uint8x16_t eq_bs = vceqq_u8(chunk, vbslash);
|
|
uint8x16_t ctrl = vcltq_u8(chunk, v20);
|
|
uint8x16_t special = vorrq_u8(vorrq_u8(eq_q, eq_bs), ctrl);
|
|
uint64x2_t s64 = vreinterpretq_u64_u8(special);
|
|
if (vgetq_lane_u64(s64, 0) || vgetq_lane_u64(s64, 1)) {
|
|
uint8_t tmp[16];
|
|
vst1q_u8(tmp, special);
|
|
for (int i = 0; i < 16; ++i) {
|
|
if (tmp[i]) return p + i;
|
|
}
|
|
}
|
|
p += 16;
|
|
}
|
|
return cj_scan_str_scalar(p, end);
|
|
}
|
|
|
|
#else
|
|
|
|
static const char *cj_scan_str(const char *p, const char *end) {
|
|
return cj_scan_str_scalar(p, end);
|
|
}
|
|
|
|
#endif /* SIMD string scan */
|
|
|
|
/* ======================================================================
|
|
* FAST NUMBER PARSING (C-style)
|
|
* ====================================================================== */
|
|
|
|
static const char *cj_parse_uint64(const char *p, const char *end,
|
|
uint64_t *result, int *overflow) {
|
|
uint64_t v = 0;
|
|
*overflow = 0;
|
|
while (p < end && (unsigned)(*p - '0') <= 9u) {
|
|
unsigned char d = (unsigned char)*p - '0';
|
|
/* Detect multiplication/addition overflow */
|
|
if (v > (UINT64_MAX - d) / 10u) {
|
|
*overflow = 1;
|
|
/* Consume remaining digits so caller sees the full token */
|
|
while (p < end && (unsigned)(*p - '0') <= 9u) ++p;
|
|
*result = UINT64_MAX;
|
|
return p;
|
|
}
|
|
v = v * 10u + (uint64_t)d;
|
|
++p;
|
|
}
|
|
*result = v;
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Parse a JSON number starting at [p, end).
|
|
* Sets *is_int, *ival (integer result), *dval (floating-point result).
|
|
* Returns pointer past the last character consumed, or NULL on error.
|
|
*
|
|
* NOTE: strtod is locale-dependent on some platforms (decimal separator).
|
|
* JSON mandates '.' as decimal separator. Callers in environments where the
|
|
* C locale may be overridden should ensure the locale is reset to "C" before
|
|
* parsing floating-point JSON values.
|
|
*/
|
|
static const char *cj_parse_number(const char *p, const char *end,
|
|
int *is_int, int64_t *ival, double *dval) {
|
|
const char *start = p;
|
|
int neg = 0;
|
|
if (p < end && *p == '-') { neg = 1; ++p; }
|
|
if (p >= end) return NULL;
|
|
|
|
uint64_t int_part = 0;
|
|
int has_frac = 0, has_exp = 0;
|
|
int int_overflow = 0;
|
|
|
|
if (*p == '0') {
|
|
++p;
|
|
} else if ((unsigned)(*p - '1') <= 8u) {
|
|
p = cj_parse_uint64(p, end, &int_part, &int_overflow);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
|
|
if (p < end && *p == '.') has_frac = 1;
|
|
if (p < end && (*p == 'e' || *p == 'E')) has_exp = 1;
|
|
|
|
if (!has_frac && !has_exp && !int_overflow) {
|
|
/* Guard signed overflow: -(int64_t)x is UB when x > INT64_MAX.
|
|
* Positive: x must fit in [0, INT64_MAX].
|
|
* Negative: magnitude must fit in [0, 2^63] i.e. <= INT64_MAX+1
|
|
* (the upper bound covers INT64_MIN = -2^63). */
|
|
int fits;
|
|
if (!neg) {
|
|
fits = (int_part <= (uint64_t)INT64_MAX);
|
|
} else {
|
|
fits = (int_part <= (uint64_t)INT64_MAX + 1u);
|
|
}
|
|
if (fits) {
|
|
int64_t sv;
|
|
if (neg && int_part == (uint64_t)INT64_MAX + 1u)
|
|
sv = INT64_MIN; /* special case: magnitude 2^63 → INT64_MIN */
|
|
else
|
|
sv = neg ? -(int64_t)int_part : (int64_t)int_part;
|
|
*is_int = 1;
|
|
*ival = sv;
|
|
*dval = (double)sv;
|
|
return p;
|
|
}
|
|
/* Magnitude doesn't fit int64_t: fall through to strtod */
|
|
}
|
|
|
|
/* Floating-point, integer overflow, or out-of-int64-range: use strtod */
|
|
char *eptr = NULL;
|
|
double d = strtod(start, &eptr);
|
|
if (eptr == start) return NULL;
|
|
*is_int = 0;
|
|
*dval = d;
|
|
*ival = (int64_t)d;
|
|
return eptr;
|
|
}
|
|
|
|
/* ======================================================================
|
|
* STRING UNESCAPING (C-style)
|
|
* ====================================================================== */
|
|
|
|
static int cj_hex4(const char *p) {
|
|
int v = 0;
|
|
for (int i = 0; i < 4; ++i) {
|
|
char c = p[i];
|
|
int d;
|
|
if (c >= '0' && c <= '9') d = c - '0';
|
|
else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
|
|
else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
|
|
else return -1;
|
|
v = (v << 4) | d;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static int cj_encode_utf8(unsigned int cp, char *buf) {
|
|
if (cp <= 0x7Fu) {
|
|
buf[0] = (char)cp;
|
|
return 1;
|
|
} else if (cp <= 0x7FFu) {
|
|
buf[0] = (char)(0xC0u | (cp >> 6));
|
|
buf[1] = (char)(0x80u | (cp & 0x3Fu));
|
|
return 2;
|
|
} else if (cp <= 0xFFFFu) {
|
|
buf[0] = (char)(0xE0u | (cp >> 12));
|
|
buf[1] = (char)(0x80u | ((cp >> 6) & 0x3Fu));
|
|
buf[2] = (char)(0x80u | (cp & 0x3Fu));
|
|
return 3;
|
|
} else if (cp <= 0x10FFFFu) {
|
|
buf[0] = (char)(0xF0u | (cp >> 18));
|
|
buf[1] = (char)(0x80u | ((cp >> 12) & 0x3Fu));
|
|
buf[2] = (char)(0x80u | ((cp >> 6) & 0x3Fu));
|
|
buf[3] = (char)(0x80u | (cp & 0x3Fu));
|
|
return 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Parse and unescape a JSON string from [p, end) where p is AFTER the
|
|
* opening '"' and end is the INCLUSIVE closing '"'.
|
|
* Caller must free() the returned pointer.
|
|
* Returns NULL on allocation failure.
|
|
*/
|
|
static char *cj_unescape_string(const char *p, const char *str_end,
|
|
size_t *out_len) {
|
|
size_t alloc = (size_t)(str_end - p) + 1;
|
|
char *out = (char *)malloc(alloc);
|
|
if (!out) return NULL;
|
|
char *dst = out;
|
|
|
|
while (p < str_end) {
|
|
/* Track start of literal run so we can copy it before processing
|
|
* the special character that ends it. */
|
|
const char *run = p;
|
|
p = cj_scan_str(p, str_end);
|
|
/* Copy non-special (literal) bytes from run to p */
|
|
if (p > run) {
|
|
size_t n = (size_t)(p - run);
|
|
memcpy(dst, run, n);
|
|
dst += n;
|
|
}
|
|
if (p >= str_end) break;
|
|
|
|
unsigned char c = (unsigned char)*p;
|
|
if (c == '"') {
|
|
break; /* should not happen - caller passes str_end=position of '"' */
|
|
} else if (c == '\\') {
|
|
++p;
|
|
if (p >= str_end) { free(out); return NULL; }
|
|
unsigned char esc = (unsigned char)*p++;
|
|
switch (esc) {
|
|
case '"': *dst++ = '"'; break;
|
|
case '\\': *dst++ = '\\'; break;
|
|
case '/': *dst++ = '/'; break;
|
|
case 'b': *dst++ = '\b'; break;
|
|
case 'f': *dst++ = '\f'; break;
|
|
case 'n': *dst++ = '\n'; break;
|
|
case 'r': *dst++ = '\r'; break;
|
|
case 't': *dst++ = '\t'; break;
|
|
case 'u': {
|
|
if (p + 4 > str_end) { free(out); return NULL; }
|
|
int cp = cj_hex4(p);
|
|
if (cp < 0) { free(out); return NULL; }
|
|
p += 4;
|
|
if (cp >= 0xD800 && cp <= 0xDBFF &&
|
|
p + 6 <= str_end && p[0] == '\\' && p[1] == 'u') {
|
|
int cp2 = cj_hex4(p + 2);
|
|
if (cp2 >= 0xDC00 && cp2 <= 0xDFFF) {
|
|
unsigned int full = 0x10000u +
|
|
(((unsigned int)cp - 0xD800u) << 10) +
|
|
((unsigned int)cp2 - 0xDC00u);
|
|
dst += cj_encode_utf8(full, dst);
|
|
p += 6;
|
|
break;
|
|
}
|
|
}
|
|
dst += cj_encode_utf8((unsigned int)cp, dst);
|
|
break;
|
|
}
|
|
default:
|
|
/* Unknown escape sequence is invalid in JSON */
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
} else if (c < 0x20u) {
|
|
/* Invalid unescaped control character in JSON string: treat as error */
|
|
free(out);
|
|
return NULL;
|
|
} else {
|
|
/* Should not be reached since scan_str stops here only for
|
|
special chars - but guard just in case */
|
|
*dst++ = (char)c;
|
|
++p;
|
|
}
|
|
}
|
|
|
|
*dst = '\0';
|
|
*out_len = (size_t)(dst - out);
|
|
return out;
|
|
}
|
|
|
|
/* ======================================================================
|
|
* FORWARD DECLARATIONS
|
|
* ====================================================================== */
|
|
|
|
/*
|
|
* tinygltf_json is the main JSON value class.
|
|
* Its data layout is C-style (all members public, named with trailing _).
|
|
*/
|
|
class tinygltf_json;
|
|
|
|
/*
|
|
* tinygltf_json_member stores one key-value pair in a JSON object.
|
|
* Must be defined AFTER tinygltf_json is complete (contains json by value).
|
|
*/
|
|
struct tinygltf_json_member;
|
|
|
|
/* ======================================================================
|
|
* tinygltf_json CLASS DECLARATION
|
|
*
|
|
* All data members are public (C-style struct convention).
|
|
* Methods are declared here and implemented after tinygltf_json_member
|
|
* is fully defined.
|
|
* ====================================================================== */
|
|
|
|
class tinygltf_json {
|
|
public:
|
|
/* ------------------------------------------------------------------
|
|
* nlohmann-compatible value type enum
|
|
* ------------------------------------------------------------------ */
|
|
enum class value_t : uint8_t {
|
|
null = 0,
|
|
boolean = 1,
|
|
number_integer = 2,
|
|
number_unsigned = 3,
|
|
number_float = 4,
|
|
string = 5,
|
|
array = 6,
|
|
object = 7,
|
|
/* Aliases from nlohmann/json that tinygltf.h references */
|
|
discarded = 8,
|
|
binary = 9
|
|
};
|
|
|
|
/* ------------------------------------------------------------------
|
|
* C-style data storage (all public for direct access from C functions)
|
|
* ------------------------------------------------------------------ */
|
|
|
|
/* Type tag: one of CJ_NULL, CJ_BOOL, CJ_INT, CJ_REAL, CJ_STRING,
|
|
CJ_ARRAY, CJ_OBJECT */
|
|
int type_;
|
|
|
|
/* Primitive values (union for space efficiency) */
|
|
union {
|
|
int64_t i_; /* CJ_INT */
|
|
double d_; /* CJ_REAL */
|
|
int b_; /* CJ_BOOL: 0 or 1 */
|
|
};
|
|
|
|
/* String storage */
|
|
char *str_; /* CJ_STRING: owned, null-terminated */
|
|
size_t str_len_;
|
|
|
|
/* Array storage: flat array of tinygltf_json objects (owned) */
|
|
tinygltf_json *arr_data_;
|
|
size_t arr_size_;
|
|
size_t arr_cap_;
|
|
|
|
/* Object storage: flat array of tinygltf_json_member (owned) */
|
|
tinygltf_json_member *obj_data_;
|
|
size_t obj_size_;
|
|
size_t obj_cap_;
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Iterator type (forward-declared here, defined later)
|
|
* ------------------------------------------------------------------ */
|
|
class iterator;
|
|
using const_iterator = iterator;
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Low-level helpers (implementations deferred until member is complete)
|
|
* ------------------------------------------------------------------ */
|
|
void init_null_();
|
|
void destroy_();
|
|
void copy_from_(const tinygltf_json &o);
|
|
tinygltf_json_member *find_member_(const char *key) const;
|
|
int obj_reserve_();
|
|
int arr_reserve_();
|
|
void make_object_();
|
|
void make_array_();
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Constructors and destructor
|
|
* ------------------------------------------------------------------ */
|
|
tinygltf_json();
|
|
tinygltf_json(std::nullptr_t);
|
|
tinygltf_json(bool b);
|
|
tinygltf_json(int i);
|
|
tinygltf_json(int64_t i);
|
|
tinygltf_json(uint64_t u);
|
|
tinygltf_json(double d);
|
|
tinygltf_json(float f);
|
|
tinygltf_json(const char *s);
|
|
tinygltf_json(const std::string &s);
|
|
tinygltf_json(const tinygltf_json &o);
|
|
tinygltf_json(tinygltf_json &&o) noexcept;
|
|
~tinygltf_json();
|
|
|
|
tinygltf_json &operator=(const tinygltf_json &o);
|
|
tinygltf_json &operator=(tinygltf_json &&o) noexcept;
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Type checks (nlohmann-compatible)
|
|
* ------------------------------------------------------------------ */
|
|
value_t type() const;
|
|
bool is_null() const { return type_ == CJ_NULL; }
|
|
bool is_boolean() const { return type_ == CJ_BOOL; }
|
|
bool is_number() const { return type_ == CJ_INT || type_ == CJ_REAL; }
|
|
bool is_number_integer() const { return type_ == CJ_INT; }
|
|
bool is_number_unsigned() const { return type_ == CJ_INT && i_ >= 0; }
|
|
bool is_number_float() const { return type_ == CJ_REAL; }
|
|
bool is_string() const { return type_ == CJ_STRING; }
|
|
bool is_array() const { return type_ == CJ_ARRAY; }
|
|
bool is_object() const { return type_ == CJ_OBJECT; }
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Value access (template specializations after class)
|
|
* ------------------------------------------------------------------ */
|
|
template<typename T> T get() const;
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Container methods
|
|
* ------------------------------------------------------------------ */
|
|
size_t size() const;
|
|
bool empty() const;
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Array operations
|
|
* ------------------------------------------------------------------ */
|
|
void push_back(tinygltf_json &&v);
|
|
void push_back(const tinygltf_json &v);
|
|
/* Ensure value is an array (no-op if already array). */
|
|
void set_array() { if (type_ != CJ_ARRAY) make_array_(); }
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Object operations
|
|
* ------------------------------------------------------------------ */
|
|
tinygltf_json &operator[](const char *key);
|
|
tinygltf_json &operator[](const std::string &key);
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Iterators
|
|
* ------------------------------------------------------------------ */
|
|
iterator begin();
|
|
iterator end();
|
|
iterator begin() const;
|
|
iterator end() const;
|
|
iterator find(const char *key) const;
|
|
iterator find(const char *key);
|
|
void erase(iterator &it);
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Static factories
|
|
* ------------------------------------------------------------------ */
|
|
static tinygltf_json object();
|
|
|
|
/* ------------------------------------------------------------------
|
|
* Serialization / deserialization
|
|
* ------------------------------------------------------------------ */
|
|
std::string dump(int indent = -1) const;
|
|
|
|
/* allow_exceptions is honoured only when TINYGLTF_JSON_USE_EXCEPTIONS is
|
|
* defined; otherwise it is accepted for API compatibility but has no
|
|
* effect — parse errors always return a null value silently. */
|
|
static tinygltf_json parse(const char *first, const char *last,
|
|
std::nullptr_t = nullptr,
|
|
bool allow_exceptions = false);
|
|
};
|
|
|
|
/* ======================================================================
|
|
* tinygltf_json_member FULL DEFINITION
|
|
* (tinygltf_json must be complete before this)
|
|
* ====================================================================== */
|
|
|
|
struct tinygltf_json_member {
|
|
char *key; /* owned, null-terminated */
|
|
size_t key_len;
|
|
tinygltf_json val; /* value stored inline */
|
|
|
|
tinygltf_json_member() : key(NULL), key_len(0), val() {}
|
|
~tinygltf_json_member() { free(key); key = NULL; }
|
|
|
|
tinygltf_json_member(const tinygltf_json_member &o)
|
|
: key(NULL), key_len(o.key_len), val(o.val) {
|
|
if (o.key) {
|
|
key = (char *)malloc(o.key_len + 1);
|
|
if (key) memcpy(key, o.key, o.key_len + 1);
|
|
else key_len = 0; /* malloc failure: keep key==NULL, len==0 */
|
|
}
|
|
}
|
|
|
|
tinygltf_json_member(tinygltf_json_member &&o) noexcept
|
|
: key(o.key), key_len(o.key_len),
|
|
val(static_cast<tinygltf_json &&>(o.val)) {
|
|
o.key = NULL;
|
|
o.key_len = 0;
|
|
}
|
|
|
|
tinygltf_json_member &operator=(const tinygltf_json_member &o) {
|
|
if (this != &o) {
|
|
free(key);
|
|
key = NULL;
|
|
key_len = o.key_len;
|
|
val = o.val;
|
|
if (o.key) {
|
|
key = (char *)malloc(o.key_len + 1);
|
|
if (key) memcpy(key, o.key, o.key_len + 1);
|
|
else key_len = 0; /* malloc failure: keep key==NULL, len==0 */
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
tinygltf_json_member &operator=(tinygltf_json_member &&o) noexcept {
|
|
if (this != &o) {
|
|
free(key);
|
|
key = o.key;
|
|
key_len = o.key_len;
|
|
val = static_cast<tinygltf_json &&>(o.val);
|
|
o.key = NULL;
|
|
o.key_len = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
/* ======================================================================
|
|
* tinygltf_json::iterator
|
|
* ====================================================================== */
|
|
|
|
class tinygltf_json::iterator {
|
|
public:
|
|
static const int MODE_ARRAY = 0;
|
|
static const int MODE_OBJECT = 1;
|
|
|
|
int mode_;
|
|
union {
|
|
tinygltf_json *arr_ptr_;
|
|
tinygltf_json_member *obj_ptr_;
|
|
};
|
|
|
|
iterator() : mode_(MODE_ARRAY), arr_ptr_(NULL) {}
|
|
|
|
explicit iterator(tinygltf_json *p)
|
|
: mode_(MODE_ARRAY), arr_ptr_(p) {}
|
|
|
|
explicit iterator(tinygltf_json_member *p)
|
|
: mode_(MODE_OBJECT), obj_ptr_(p) {}
|
|
|
|
/* Pre-increment */
|
|
iterator &operator++() {
|
|
if (mode_ == MODE_ARRAY) ++arr_ptr_;
|
|
else ++obj_ptr_;
|
|
return *this;
|
|
}
|
|
|
|
/* Post-increment */
|
|
iterator operator++(int) {
|
|
iterator tmp = *this;
|
|
++(*this);
|
|
return tmp;
|
|
}
|
|
|
|
tinygltf_json &operator*() {
|
|
return (mode_ == MODE_ARRAY) ? *arr_ptr_ : obj_ptr_->val;
|
|
}
|
|
const tinygltf_json &operator*() const {
|
|
return (mode_ == MODE_ARRAY) ? *arr_ptr_ : obj_ptr_->val;
|
|
}
|
|
tinygltf_json *operator->() {
|
|
return (mode_ == MODE_ARRAY) ? arr_ptr_ : &obj_ptr_->val;
|
|
}
|
|
const tinygltf_json *operator->() const {
|
|
return (mode_ == MODE_ARRAY) ? arr_ptr_ : &obj_ptr_->val;
|
|
}
|
|
|
|
std::string key() const {
|
|
if (mode_ == MODE_OBJECT && obj_ptr_ && obj_ptr_->key)
|
|
return std::string(obj_ptr_->key, obj_ptr_->key_len);
|
|
return std::string();
|
|
}
|
|
|
|
tinygltf_json &value() {
|
|
return operator*();
|
|
}
|
|
const tinygltf_json &value() const {
|
|
return operator*();
|
|
}
|
|
|
|
bool operator==(const iterator &o) const {
|
|
if (mode_ != o.mode_) return false;
|
|
return (mode_ == MODE_ARRAY)
|
|
? (arr_ptr_ == o.arr_ptr_)
|
|
: (obj_ptr_ == o.obj_ptr_);
|
|
}
|
|
bool operator!=(const iterator &o) const { return !(*this == o); }
|
|
};
|
|
|
|
/* ======================================================================
|
|
* tinygltf_json METHOD IMPLEMENTATIONS
|
|
* (Now that tinygltf_json_member is fully defined)
|
|
* ====================================================================== */
|
|
|
|
inline void tinygltf_json::init_null_() {
|
|
type_ = CJ_NULL;
|
|
i_ = 0;
|
|
str_ = NULL; str_len_ = 0;
|
|
arr_data_ = NULL; arr_size_ = 0; arr_cap_ = 0;
|
|
obj_data_ = NULL; obj_size_ = 0; obj_cap_ = 0;
|
|
}
|
|
|
|
inline void tinygltf_json::destroy_() {
|
|
if (type_ == CJ_STRING) {
|
|
free(str_);
|
|
str_ = NULL;
|
|
} else if (type_ == CJ_ARRAY) {
|
|
for (size_t i = 0; i < arr_size_; ++i)
|
|
arr_data_[i].~tinygltf_json();
|
|
free(arr_data_);
|
|
arr_data_ = NULL;
|
|
arr_size_ = arr_cap_ = 0;
|
|
} else if (type_ == CJ_OBJECT) {
|
|
for (size_t i = 0; i < obj_size_; ++i)
|
|
obj_data_[i].~tinygltf_json_member();
|
|
free(obj_data_);
|
|
obj_data_ = NULL;
|
|
obj_size_ = obj_cap_ = 0;
|
|
}
|
|
type_ = CJ_NULL;
|
|
}
|
|
|
|
inline void tinygltf_json::copy_from_(const tinygltf_json &o) {
|
|
type_ = o.type_;
|
|
i_ = o.i_;
|
|
str_ = NULL; str_len_ = 0;
|
|
arr_data_ = NULL; arr_size_ = 0; arr_cap_ = 0;
|
|
obj_data_ = NULL; obj_size_ = 0; obj_cap_ = 0;
|
|
|
|
if (o.type_ == CJ_STRING) {
|
|
if (o.str_) {
|
|
str_len_ = o.str_len_;
|
|
str_ = (char *)malloc(str_len_ + 1);
|
|
if (str_) memcpy(str_, o.str_, str_len_ + 1);
|
|
else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */
|
|
}
|
|
} else if (o.type_ == CJ_ARRAY) {
|
|
if (o.arr_size_ > 0) {
|
|
/* Guard against multiplication overflow */
|
|
if (o.arr_size_ <= SIZE_MAX / sizeof(tinygltf_json)) {
|
|
arr_data_ = (tinygltf_json *)malloc(
|
|
o.arr_size_ * sizeof(tinygltf_json));
|
|
if (arr_data_) {
|
|
arr_size_ = 0;
|
|
arr_cap_ = o.arr_size_;
|
|
for (size_t i = 0; i < o.arr_size_; ++i) {
|
|
new (&arr_data_[i]) tinygltf_json(o.arr_data_[i]);
|
|
++arr_size_;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (o.type_ == CJ_OBJECT) {
|
|
if (o.obj_size_ > 0) {
|
|
/* Guard against multiplication overflow */
|
|
if (o.obj_size_ <= SIZE_MAX / sizeof(tinygltf_json_member)) {
|
|
obj_data_ = (tinygltf_json_member *)malloc(
|
|
o.obj_size_ * sizeof(tinygltf_json_member));
|
|
if (obj_data_) {
|
|
obj_size_ = 0;
|
|
obj_cap_ = o.obj_size_;
|
|
for (size_t i = 0; i < o.obj_size_; ++i) {
|
|
new (&obj_data_[i]) tinygltf_json_member(o.obj_data_[i]);
|
|
++obj_size_;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
inline tinygltf_json_member *tinygltf_json::find_member_(
|
|
const char *key) const {
|
|
if (!key) return NULL;
|
|
size_t klen = strlen(key);
|
|
for (size_t i = 0; i < obj_size_; ++i) {
|
|
/* Guard against NULL key (can occur if malloc failed during insert) */
|
|
if (obj_data_[i].key == NULL) continue;
|
|
if (obj_data_[i].key_len == klen &&
|
|
memcmp(obj_data_[i].key, key, klen) == 0)
|
|
return &obj_data_[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
inline int tinygltf_json::obj_reserve_() {
|
|
if (obj_size_ < obj_cap_) return 1;
|
|
size_t new_cap = obj_cap_ ? obj_cap_ * 2 : 8;
|
|
/* Guard against allocation overflow */
|
|
if (new_cap > (size_t)0x7FFFFFFF / sizeof(tinygltf_json_member)) return 0;
|
|
tinygltf_json_member *nd = (tinygltf_json_member *)malloc(
|
|
new_cap * sizeof(tinygltf_json_member));
|
|
if (!nd) return 0;
|
|
for (size_t i = 0; i < obj_size_; ++i) {
|
|
new (&nd[i]) tinygltf_json_member(
|
|
static_cast<tinygltf_json_member &&>(obj_data_[i]));
|
|
obj_data_[i].~tinygltf_json_member();
|
|
}
|
|
free(obj_data_);
|
|
obj_data_ = nd;
|
|
obj_cap_ = new_cap;
|
|
return 1;
|
|
}
|
|
|
|
inline int tinygltf_json::arr_reserve_() {
|
|
if (arr_size_ < arr_cap_) return 1;
|
|
size_t new_cap = arr_cap_ ? arr_cap_ * 2 : 8;
|
|
/* Guard against allocation overflow */
|
|
if (new_cap > (size_t)0x7FFFFFFF / sizeof(tinygltf_json)) return 0;
|
|
tinygltf_json *nd = (tinygltf_json *)malloc(
|
|
new_cap * sizeof(tinygltf_json));
|
|
if (!nd) return 0;
|
|
for (size_t i = 0; i < arr_size_; ++i) {
|
|
new (&nd[i]) tinygltf_json(
|
|
static_cast<tinygltf_json &&>(arr_data_[i]));
|
|
arr_data_[i].destroy_();
|
|
arr_data_[i].type_ = CJ_NULL;
|
|
}
|
|
free(arr_data_);
|
|
arr_data_ = nd;
|
|
arr_cap_ = new_cap;
|
|
return 1;
|
|
}
|
|
|
|
inline void tinygltf_json::make_object_() {
|
|
destroy_();
|
|
type_ = CJ_OBJECT;
|
|
}
|
|
|
|
inline void tinygltf_json::make_array_() {
|
|
destroy_();
|
|
type_ = CJ_ARRAY;
|
|
}
|
|
|
|
/* Constructors */
|
|
inline tinygltf_json::tinygltf_json() { init_null_(); }
|
|
inline tinygltf_json::tinygltf_json(std::nullptr_t) { init_null_(); }
|
|
inline tinygltf_json::tinygltf_json(bool b) { init_null_(); type_ = CJ_BOOL; b_ = b ? 1 : 0; }
|
|
inline tinygltf_json::tinygltf_json(int i) { init_null_(); type_ = CJ_INT; i_ = (int64_t)i; }
|
|
inline tinygltf_json::tinygltf_json(int64_t i) { init_null_(); type_ = CJ_INT; i_ = i; }
|
|
inline tinygltf_json::tinygltf_json(uint64_t u) {
|
|
init_null_();
|
|
if (u <= (uint64_t)INT64_MAX) {
|
|
type_ = CJ_INT;
|
|
i_ = (int64_t)u;
|
|
} else {
|
|
type_ = CJ_REAL;
|
|
d_ = (double)u;
|
|
}
|
|
}
|
|
inline tinygltf_json::tinygltf_json(double d) { init_null_(); type_ = CJ_REAL; d_ = d; }
|
|
inline tinygltf_json::tinygltf_json(float f) { init_null_(); type_ = CJ_REAL; d_ = (double)f; }
|
|
|
|
inline tinygltf_json::tinygltf_json(const char *s) {
|
|
init_null_();
|
|
if (s) {
|
|
type_ = CJ_STRING;
|
|
str_len_ = strlen(s);
|
|
str_ = (char *)malloc(str_len_ + 1);
|
|
if (str_) memcpy(str_, s, str_len_ + 1);
|
|
else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */
|
|
}
|
|
}
|
|
|
|
inline tinygltf_json::tinygltf_json(const std::string &s) {
|
|
init_null_();
|
|
type_ = CJ_STRING;
|
|
str_len_ = s.size();
|
|
str_ = (char *)malloc(str_len_ + 1);
|
|
if (str_) memcpy(str_, s.c_str(), str_len_ + 1);
|
|
else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */
|
|
}
|
|
|
|
inline tinygltf_json::tinygltf_json(const tinygltf_json &o) {
|
|
init_null_();
|
|
copy_from_(o);
|
|
}
|
|
|
|
inline tinygltf_json::tinygltf_json(tinygltf_json &&o) noexcept {
|
|
type_ = o.type_;
|
|
i_ = o.i_;
|
|
str_ = o.str_;
|
|
str_len_ = o.str_len_;
|
|
arr_data_ = o.arr_data_; arr_size_ = o.arr_size_; arr_cap_ = o.arr_cap_;
|
|
obj_data_ = o.obj_data_; obj_size_ = o.obj_size_; obj_cap_ = o.obj_cap_;
|
|
o.type_ = CJ_NULL;
|
|
o.str_ = NULL;
|
|
o.arr_data_ = NULL; o.arr_size_ = 0; o.arr_cap_ = 0;
|
|
o.obj_data_ = NULL; o.obj_size_ = 0; o.obj_cap_ = 0;
|
|
}
|
|
|
|
inline tinygltf_json::~tinygltf_json() { destroy_(); }
|
|
|
|
inline tinygltf_json &tinygltf_json::operator=(const tinygltf_json &o) {
|
|
if (this != &o) { destroy_(); copy_from_(o); }
|
|
return *this;
|
|
}
|
|
|
|
inline tinygltf_json &tinygltf_json::operator=(tinygltf_json &&o) noexcept {
|
|
if (this != &o) {
|
|
destroy_();
|
|
type_ = o.type_;
|
|
i_ = o.i_;
|
|
str_ = o.str_;
|
|
str_len_ = o.str_len_;
|
|
arr_data_ = o.arr_data_; arr_size_ = o.arr_size_; arr_cap_ = o.arr_cap_;
|
|
obj_data_ = o.obj_data_; obj_size_ = o.obj_size_; obj_cap_ = o.obj_cap_;
|
|
o.type_ = CJ_NULL;
|
|
o.str_ = NULL;
|
|
o.arr_data_ = NULL; o.arr_size_ = 0; o.arr_cap_ = 0;
|
|
o.obj_data_ = NULL; o.obj_size_ = 0; o.obj_cap_ = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
inline tinygltf_json::value_t tinygltf_json::type() const {
|
|
switch (type_) {
|
|
case CJ_NULL: return value_t::null;
|
|
case CJ_BOOL: return value_t::boolean;
|
|
case CJ_INT: return i_ >= 0 ? value_t::number_unsigned
|
|
: value_t::number_integer;
|
|
case CJ_REAL: return value_t::number_float;
|
|
case CJ_STRING: return value_t::string;
|
|
case CJ_ARRAY: return value_t::array;
|
|
case CJ_OBJECT: return value_t::object;
|
|
default: return value_t::null;
|
|
}
|
|
}
|
|
|
|
inline size_t tinygltf_json::size() const {
|
|
if (type_ == CJ_ARRAY) return arr_size_;
|
|
if (type_ == CJ_OBJECT) return obj_size_;
|
|
return 0;
|
|
}
|
|
|
|
inline bool tinygltf_json::empty() const {
|
|
if (type_ == CJ_ARRAY) return arr_size_ == 0;
|
|
if (type_ == CJ_OBJECT) return obj_size_ == 0;
|
|
return true;
|
|
}
|
|
|
|
inline void tinygltf_json::push_back(tinygltf_json &&v) {
|
|
if (type_ != CJ_ARRAY) make_array_();
|
|
if (!arr_reserve_()) return;
|
|
new (&arr_data_[arr_size_]) tinygltf_json(
|
|
static_cast<tinygltf_json &&>(v));
|
|
++arr_size_;
|
|
}
|
|
|
|
inline void tinygltf_json::push_back(const tinygltf_json &v) {
|
|
push_back(tinygltf_json(v));
|
|
}
|
|
|
|
inline tinygltf_json &tinygltf_json::operator[](const char *key) {
|
|
/* Degraded-mode fallback for API misuse (null key) or OOM.
|
|
* Returns a reference to a shared static null object. This is the same
|
|
* best-effort pattern used for the OOM path below.
|
|
* CAUTION: the static is shared across calls; modifications through this
|
|
* reference persist (same caveat as the OOM fallback). Callers should
|
|
* treat a null-key or OOM insert as a no-op. */
|
|
static tinygltf_json null_fallback;
|
|
if (!key) return null_fallback;
|
|
if (type_ != CJ_OBJECT) make_object_();
|
|
tinygltf_json_member *m = find_member_(key);
|
|
if (m) return m->val;
|
|
if (!obj_reserve_()) return null_fallback;
|
|
tinygltf_json_member *nm = &obj_data_[obj_size_];
|
|
new (nm) tinygltf_json_member();
|
|
size_t klen = strlen(key);
|
|
nm->key = (char *)malloc(klen + 1);
|
|
if (!nm->key) {
|
|
/* Roll back insertion on key allocation failure: destroy the
|
|
* placement-new'd member and do not bump obj_size_, keeping the
|
|
* object in a consistent state. */
|
|
nm->~tinygltf_json_member();
|
|
return null_fallback;
|
|
}
|
|
memcpy(nm->key, key, klen + 1);
|
|
nm->key_len = klen;
|
|
++obj_size_;
|
|
return nm->val;
|
|
}
|
|
|
|
inline tinygltf_json &tinygltf_json::operator[](const std::string &key) {
|
|
return operator[](key.c_str());
|
|
}
|
|
|
|
inline tinygltf_json::iterator tinygltf_json::begin() {
|
|
if (type_ == CJ_ARRAY) return iterator(arr_data_);
|
|
if (type_ == CJ_OBJECT) return iterator(obj_data_);
|
|
return iterator((tinygltf_json *)NULL);
|
|
}
|
|
inline tinygltf_json::iterator tinygltf_json::end() {
|
|
if (type_ == CJ_ARRAY) return iterator(arr_data_ + arr_size_);
|
|
if (type_ == CJ_OBJECT) return iterator(obj_data_ + obj_size_);
|
|
return iterator((tinygltf_json *)NULL);
|
|
}
|
|
inline tinygltf_json::iterator tinygltf_json::begin() const {
|
|
tinygltf_json *self = const_cast<tinygltf_json *>(this);
|
|
return self->begin();
|
|
}
|
|
inline tinygltf_json::iterator tinygltf_json::end() const {
|
|
tinygltf_json *self = const_cast<tinygltf_json *>(this);
|
|
return self->end();
|
|
}
|
|
|
|
inline tinygltf_json::iterator tinygltf_json::find(const char *key) {
|
|
if (type_ == CJ_OBJECT) {
|
|
tinygltf_json_member *m = find_member_(key);
|
|
if (m) return iterator(m);
|
|
return iterator(obj_data_ + obj_size_);
|
|
}
|
|
return iterator((tinygltf_json *)NULL);
|
|
}
|
|
inline tinygltf_json::iterator tinygltf_json::find(const char *key) const {
|
|
return const_cast<tinygltf_json *>(this)->find(key);
|
|
}
|
|
|
|
inline void tinygltf_json::erase(tinygltf_json::iterator &it) {
|
|
if (type_ != CJ_OBJECT || it.mode_ != iterator::MODE_OBJECT) return;
|
|
ptrdiff_t idx = it.obj_ptr_ - obj_data_;
|
|
if (idx < 0 || (size_t)idx >= obj_size_) return;
|
|
obj_data_[idx].~tinygltf_json_member();
|
|
for (size_t i = (size_t)idx; i + 1 < obj_size_; ++i) {
|
|
new (&obj_data_[i]) tinygltf_json_member(
|
|
static_cast<tinygltf_json_member &&>(obj_data_[i + 1]));
|
|
obj_data_[i + 1].~tinygltf_json_member();
|
|
}
|
|
--obj_size_;
|
|
it = end();
|
|
}
|
|
|
|
inline tinygltf_json tinygltf_json::object() {
|
|
tinygltf_json j;
|
|
j.make_object_();
|
|
return j;
|
|
}
|
|
|
|
/* ======================================================================
|
|
* get<T>() specializations
|
|
* ====================================================================== */
|
|
|
|
template<> inline double tinygltf_json::get<double>() const {
|
|
if (type_ == CJ_REAL) return d_;
|
|
if (type_ == CJ_INT) return (double)i_;
|
|
return 0.0;
|
|
}
|
|
|
|
template<> inline int tinygltf_json::get<int>() const {
|
|
if (type_ == CJ_INT) return (int)i_;
|
|
if (type_ == CJ_REAL) return (int)d_;
|
|
return 0;
|
|
}
|
|
|
|
template<> inline int64_t tinygltf_json::get<int64_t>() const {
|
|
if (type_ == CJ_INT) return i_;
|
|
if (type_ == CJ_REAL) return (int64_t)d_;
|
|
return 0;
|
|
}
|
|
|
|
template<> inline uint64_t tinygltf_json::get<uint64_t>() const {
|
|
if (type_ == CJ_INT) return (uint64_t)i_;
|
|
if (type_ == CJ_REAL) return (uint64_t)d_;
|
|
return 0;
|
|
}
|
|
|
|
template<> inline bool tinygltf_json::get<bool>() const {
|
|
if (type_ == CJ_BOOL) return b_ != 0;
|
|
return false;
|
|
}
|
|
|
|
template<> inline std::string tinygltf_json::get<std::string>() const {
|
|
if (type_ == CJ_STRING && str_)
|
|
return std::string(str_, str_len_);
|
|
return std::string();
|
|
}
|
|
|
|
/* Primary template for any T not explicitly specialised (e.g. size_t on
|
|
* platforms where it is a distinct type from all of the above, such as
|
|
* macOS 64-bit where uint64_t=unsigned long long but size_t=unsigned long).
|
|
* Falls back to a static_cast from the stored integer or floating-point value.
|
|
* For unsigned T: negative integer values produce 0 rather than wrapping. */
|
|
template<typename T>
|
|
inline T tinygltf_json::get() const {
|
|
if (type_ == CJ_INT) {
|
|
/* Guard unsigned types against sign-extension of negative values */
|
|
if ((T)(-1) > (T)(0) && i_ < 0) return (T)(0);
|
|
return static_cast<T>(i_);
|
|
}
|
|
if (type_ == CJ_REAL) return static_cast<T>(d_);
|
|
if (type_ == CJ_BOOL) return static_cast<T>(b_);
|
|
return T();
|
|
}
|
|
|
|
/* ======================================================================
|
|
* PARSER (C-style iterative, explicit frame stack)
|
|
*
|
|
* Uses an explicit cj_frame stack instead of C recursion so that deeply
|
|
* nested JSON cannot overflow the call stack. CJ_MAX_ITER limits both
|
|
* the container nesting depth (stack size) and serves as the iteration
|
|
* safety budget: a malformed input that keeps pushing containers without
|
|
* consuming content is rejected once the stack is full.
|
|
* ====================================================================== */
|
|
|
|
/* Maximum container nesting depth (size of the explicit frame stack) */
|
|
#define CJ_MAX_ITER 512
|
|
|
|
/* One entry per open container (array or object) on the explicit stack */
|
|
struct cj_frame {
|
|
tinygltf_json *container; /* The array or object being populated */
|
|
int is_object; /* 0 = array, 1 = object */
|
|
};
|
|
|
|
struct cj_parse_ctx {
|
|
const char *cur;
|
|
const char *end;
|
|
int err;
|
|
char errmsg[256];
|
|
};
|
|
|
|
static void cj_ctx_error(cj_parse_ctx *ctx, const char *msg) {
|
|
if (!ctx->err) {
|
|
ctx->err = 1;
|
|
strncpy(ctx->errmsg, msg, sizeof(ctx->errmsg) - 1);
|
|
ctx->errmsg[sizeof(ctx->errmsg) - 1] = '\0';
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parse a JSON string from the current position.
|
|
* cur must point to the opening '"'.
|
|
* On success, advances cur past the closing '"' and sets *out_str (owned).
|
|
*/
|
|
static void cj_parse_string_to(cj_parse_ctx *ctx, char **out_str,
|
|
size_t *out_len) {
|
|
assert(ctx->cur < ctx->end && *ctx->cur == '"');
|
|
++ctx->cur; /* skip opening '"' */
|
|
|
|
const char *p = ctx->cur;
|
|
|
|
/* Fast path: find closing '"' without escapes */
|
|
while (p < ctx->end) {
|
|
p = cj_scan_str(p, ctx->end);
|
|
if (p >= ctx->end) {
|
|
cj_ctx_error(ctx, "unterminated string");
|
|
*out_str = NULL; *out_len = 0;
|
|
return;
|
|
}
|
|
if (*p == '"') {
|
|
/* No escapes: copy directly */
|
|
size_t len = (size_t)(p - ctx->cur);
|
|
char *s = (char *)malloc(len + 1);
|
|
if (!s) { cj_ctx_error(ctx, "out of memory"); *out_str = NULL; *out_len = 0; return; }
|
|
memcpy(s, ctx->cur, len);
|
|
s[len] = '\0';
|
|
*out_str = s;
|
|
*out_len = len;
|
|
ctx->cur = p + 1;
|
|
return;
|
|
}
|
|
if (*p == '\\') {
|
|
/* Has escapes: find true end, then unescape */
|
|
const char *scan = p;
|
|
while (scan < ctx->end) {
|
|
scan = cj_scan_str(scan, ctx->end);
|
|
if (scan >= ctx->end) { cj_ctx_error(ctx, "unterminated string"); *out_str = NULL; *out_len = 0; return; }
|
|
if (*scan == '"') break;
|
|
if (*scan == '\\') {
|
|
++scan;
|
|
if (scan >= ctx->end) { cj_ctx_error(ctx, "truncated escape"); *out_str = NULL; *out_len = 0; return; }
|
|
if (*scan == 'u') {
|
|
/* \uXXXX requires exactly 4 hex digits after 'u' */
|
|
if (scan + 5 > ctx->end) {
|
|
cj_ctx_error(ctx, "truncated \\u escape");
|
|
*out_str = NULL; *out_len = 0; return;
|
|
}
|
|
scan += 5;
|
|
} else {
|
|
++scan;
|
|
}
|
|
} else {
|
|
/* cj_scan_str stopped at a control char (<0x20): invalid JSON */
|
|
cj_ctx_error(ctx, "invalid control character in string");
|
|
*out_str = NULL; *out_len = 0; return;
|
|
}
|
|
}
|
|
/* After the loop, scan must point to the closing '"' */
|
|
if (scan >= ctx->end) {
|
|
cj_ctx_error(ctx, "unterminated string");
|
|
*out_str = NULL; *out_len = 0; return;
|
|
}
|
|
if (ctx->err) { *out_str = NULL; *out_len = 0; return; }
|
|
*out_str = cj_unescape_string(ctx->cur, scan, out_len);
|
|
if (!*out_str) { cj_ctx_error(ctx, "string unescape failed"); }
|
|
ctx->cur = scan + 1;
|
|
return;
|
|
}
|
|
/* Control char (< 0x20) - treat as parse error (invalid JSON) */
|
|
cj_ctx_error(ctx, "invalid control character in string");
|
|
*out_str = NULL; *out_len = 0;
|
|
return;
|
|
}
|
|
cj_ctx_error(ctx, "unterminated string");
|
|
*out_str = NULL; *out_len = 0;
|
|
}
|
|
|
|
/*
|
|
* Parse a scalar JSON value (string, number, bool, null) into *slot.
|
|
* ctx->cur must point to the first character of the value (whitespace
|
|
* already consumed).
|
|
*/
|
|
static void cj_parse_scalar(cj_parse_ctx *ctx, tinygltf_json *slot) {
|
|
char c = *ctx->cur;
|
|
|
|
if (c == '"') {
|
|
char *s = NULL; size_t slen = 0;
|
|
cj_parse_string_to(ctx, &s, &slen);
|
|
if (ctx->err || !s) { free(s); slot->destroy_(); slot->init_null_(); return; }
|
|
slot->destroy_(); slot->init_null_();
|
|
slot->type_ = CJ_STRING; slot->str_ = s; slot->str_len_ = slen;
|
|
} else if (c == 't') {
|
|
if (ctx->end - ctx->cur >= 4 && memcmp(ctx->cur, "true", 4) == 0) {
|
|
ctx->cur += 4;
|
|
slot->destroy_(); slot->init_null_();
|
|
slot->type_ = CJ_BOOL; slot->b_ = 1;
|
|
} else { cj_ctx_error(ctx, "invalid literal 'true'"); }
|
|
} else if (c == 'f') {
|
|
if (ctx->end - ctx->cur >= 5 && memcmp(ctx->cur, "false", 5) == 0) {
|
|
ctx->cur += 5;
|
|
slot->destroy_(); slot->init_null_();
|
|
slot->type_ = CJ_BOOL; slot->b_ = 0;
|
|
} else { cj_ctx_error(ctx, "invalid literal 'false'"); }
|
|
} else if (c == 'n') {
|
|
if (ctx->end - ctx->cur >= 4 && memcmp(ctx->cur, "null", 4) == 0) {
|
|
ctx->cur += 4;
|
|
slot->destroy_(); slot->init_null_();
|
|
} else { cj_ctx_error(ctx, "invalid literal 'null'"); }
|
|
} else if (c == '-' || (c >= '0' && c <= '9')) {
|
|
int is_int = 0; int64_t ival = 0; double dval = 0.0;
|
|
const char *next = cj_parse_number(ctx->cur, ctx->end, &is_int, &ival, &dval);
|
|
if (!next) { cj_ctx_error(ctx, "invalid number"); return; }
|
|
ctx->cur = next;
|
|
slot->destroy_(); slot->init_null_();
|
|
if (is_int) { slot->type_ = CJ_INT; slot->i_ = ival; }
|
|
else { slot->type_ = CJ_REAL; slot->d_ = dval; }
|
|
} else {
|
|
char errbuf[64];
|
|
snprintf(errbuf, sizeof(errbuf), "unexpected character '%c' (0x%02X)",
|
|
(unsigned char)c >= 0x20u ? c : '?', (unsigned char)c);
|
|
cj_ctx_error(ctx, errbuf);
|
|
slot->destroy_(); slot->init_null_();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cj_parse_json -- iterative JSON parser.
|
|
*
|
|
* Parses one complete JSON value from ctx into *root using an explicit
|
|
* cj_frame[CJ_MAX_ITER] stack instead of C recursion. No C stack frames
|
|
* are consumed for nesting; the only stack growth comes from the fixed-size
|
|
* cj_frame array declared as a local variable here.
|
|
*
|
|
* Loop structure:
|
|
* after_val == 0 -> parse the next JSON value into *slot
|
|
* after_val == 1 -> a value was just completed; handle ',' / ']' / '}'
|
|
*
|
|
* CJ_MAX_ITER caps the container nesting depth. Each '{' or '[' increments
|
|
* depth; reaching the cap produces an error rather than an out-of-bounds
|
|
* write.
|
|
*/
|
|
static void cj_parse_json(cj_parse_ctx *ctx, tinygltf_json *root) {
|
|
cj_frame stack[CJ_MAX_ITER];
|
|
int depth = 0; /* frames in use */
|
|
int after_val = 0; /* 0 = need value, 1 = value just finished */
|
|
|
|
/* Where to write the next parsed value */
|
|
tinygltf_json *slot = root;
|
|
|
|
for (;;) {
|
|
if (ctx->err) break;
|
|
|
|
/* ---------------------------------------------------------------
|
|
* POST-VALUE: handle separator / closing bracket
|
|
* ------------------------------------------------------------- */
|
|
if (after_val) {
|
|
after_val = 0;
|
|
|
|
if (depth == 0) {
|
|
/* Root value complete: ensure only trailing whitespace remains */
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur != ctx->end) {
|
|
cj_ctx_error(ctx, "trailing non-whitespace after JSON root value");
|
|
}
|
|
break;
|
|
}
|
|
|
|
cj_frame *f = &stack[depth - 1];
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur >= ctx->end) {
|
|
cj_ctx_error(ctx, "unexpected EOF after value"); break;
|
|
}
|
|
|
|
if (!f->is_object) {
|
|
/* ---- Array: expect ',' or ']' ---- */
|
|
if (*ctx->cur == ',') {
|
|
++ctx->cur;
|
|
/* Allocate next element slot */
|
|
tinygltf_json *cont = f->container;
|
|
if (!cont->arr_reserve_()) { cj_ctx_error(ctx, "OOM"); break; }
|
|
new (&cont->arr_data_[cont->arr_size_]) tinygltf_json();
|
|
slot = &cont->arr_data_[cont->arr_size_];
|
|
++cont->arr_size_;
|
|
/* Loop back to parse the element value */
|
|
} else if (*ctx->cur == ']') {
|
|
++ctx->cur;
|
|
--depth;
|
|
after_val = 1; /* the array itself is now the completed value */
|
|
} else {
|
|
cj_ctx_error(ctx, "expected ',' or ']' in array"); break;
|
|
}
|
|
} else {
|
|
/* ---- Object: expect ',' or '}' ---- */
|
|
if (*ctx->cur == ',') {
|
|
++ctx->cur;
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur >= ctx->end) {
|
|
cj_ctx_error(ctx, "unexpected EOF in object"); break;
|
|
}
|
|
if (*ctx->cur != '"') {
|
|
cj_ctx_error(ctx, "expected object key after ','"); break;
|
|
}
|
|
/* Parse key and allocate member slot */
|
|
char *k = NULL; size_t kl = 0;
|
|
cj_parse_string_to(ctx, &k, &kl);
|
|
if (ctx->err || !k) { free(k); break; }
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur >= ctx->end || *ctx->cur != ':') {
|
|
free(k); cj_ctx_error(ctx, "expected ':' in object"); break;
|
|
}
|
|
++ctx->cur;
|
|
tinygltf_json *cont = f->container;
|
|
if (!cont->obj_reserve_()) { free(k); cj_ctx_error(ctx, "OOM"); break; }
|
|
tinygltf_json_member *m = &cont->obj_data_[cont->obj_size_];
|
|
new (m) tinygltf_json_member();
|
|
m->key = k; m->key_len = kl;
|
|
++cont->obj_size_;
|
|
slot = &m->val;
|
|
/* Loop back to parse the member value */
|
|
} else if (*ctx->cur == '}') {
|
|
++ctx->cur;
|
|
--depth;
|
|
after_val = 1; /* the object itself is now the completed value */
|
|
} else {
|
|
cj_ctx_error(ctx, "expected ',' or '}' in object"); break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------
|
|
* PARSE VALUE: read *slot from ctx->cur
|
|
* ------------------------------------------------------------- */
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur >= ctx->end) {
|
|
if (depth == 0) break; /* trailing whitespace on root value is ok */
|
|
cj_ctx_error(ctx, "unexpected EOF"); break;
|
|
}
|
|
|
|
char c = *ctx->cur;
|
|
|
|
if (c == '{') {
|
|
/* ---- Begin object ---- */
|
|
if (depth >= CJ_MAX_ITER) {
|
|
cj_ctx_error(ctx, "nesting limit exceeded"); break;
|
|
}
|
|
++ctx->cur;
|
|
slot->destroy_(); slot->init_null_(); slot->type_ = CJ_OBJECT;
|
|
|
|
stack[depth].container = slot;
|
|
stack[depth].is_object = 1;
|
|
++depth;
|
|
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur >= ctx->end) { cj_ctx_error(ctx, "EOF in object"); break; }
|
|
if (*ctx->cur == '}') { ++ctx->cur; --depth; after_val = 1; continue; }
|
|
|
|
/* Parse first key */
|
|
if (*ctx->cur != '"') { cj_ctx_error(ctx, "expected key in object"); break; }
|
|
{
|
|
char *k = NULL; size_t kl = 0;
|
|
cj_parse_string_to(ctx, &k, &kl);
|
|
if (ctx->err || !k) { free(k); break; }
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur >= ctx->end || *ctx->cur != ':') {
|
|
free(k); cj_ctx_error(ctx, "expected ':' in object"); break;
|
|
}
|
|
++ctx->cur;
|
|
if (!slot->obj_reserve_()) { free(k); cj_ctx_error(ctx, "OOM"); break; }
|
|
tinygltf_json_member *m = &slot->obj_data_[slot->obj_size_];
|
|
new (m) tinygltf_json_member();
|
|
m->key = k; m->key_len = kl;
|
|
++slot->obj_size_;
|
|
slot = &m->val; /* next iteration parses the first value */
|
|
}
|
|
|
|
} else if (c == '[') {
|
|
/* ---- Begin array ---- */
|
|
if (depth >= CJ_MAX_ITER) {
|
|
cj_ctx_error(ctx, "nesting limit exceeded"); break;
|
|
}
|
|
++ctx->cur;
|
|
slot->destroy_(); slot->init_null_(); slot->type_ = CJ_ARRAY;
|
|
|
|
stack[depth].container = slot;
|
|
stack[depth].is_object = 0;
|
|
++depth;
|
|
|
|
ctx->cur = cj_skip_ws(ctx->cur, ctx->end);
|
|
if (ctx->cur >= ctx->end) { cj_ctx_error(ctx, "EOF in array"); break; }
|
|
if (*ctx->cur == ']') { ++ctx->cur; --depth; after_val = 1; continue; }
|
|
|
|
/* Allocate first element slot */
|
|
{
|
|
tinygltf_json *cont = stack[depth - 1].container;
|
|
if (!cont->arr_reserve_()) { cj_ctx_error(ctx, "OOM"); break; }
|
|
new (&cont->arr_data_[cont->arr_size_]) tinygltf_json();
|
|
slot = &cont->arr_data_[cont->arr_size_];
|
|
++cont->arr_size_;
|
|
}
|
|
/* next iteration parses the first element */
|
|
|
|
} else {
|
|
/* ---- Scalar value ---- */
|
|
cj_parse_scalar(ctx, slot);
|
|
after_val = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ======================================================================
|
|
* SERIALIZATION (C-style string builder)
|
|
* ====================================================================== */
|
|
|
|
struct cj_strbuf {
|
|
char *data;
|
|
size_t len;
|
|
size_t cap;
|
|
};
|
|
|
|
static int cj_strbuf_init(cj_strbuf *sb, size_t initial) {
|
|
sb->data = (char *)malloc(initial);
|
|
sb->len = 0;
|
|
sb->cap = initial;
|
|
return sb->data ? 1 : 0;
|
|
}
|
|
|
|
static void cj_strbuf_free_data(cj_strbuf *sb) {
|
|
free(sb->data);
|
|
sb->data = NULL;
|
|
sb->len = sb->cap = 0;
|
|
}
|
|
|
|
static int cj_strbuf_grow(cj_strbuf *sb, size_t extra) {
|
|
/* Guard against size_t overflow in needed = sb->len + extra */
|
|
if (extra > (size_t)-1 - sb->len) return 0;
|
|
size_t needed = sb->len + extra;
|
|
if (needed <= sb->cap) return 1;
|
|
size_t new_cap = sb->cap * 2;
|
|
if (new_cap < needed) {
|
|
/* Guard against overflow in needed + 256 */
|
|
if (needed > SIZE_MAX - 256) return 0;
|
|
new_cap = needed + 256;
|
|
}
|
|
char *nd = (char *)realloc(sb->data, new_cap);
|
|
if (!nd) return 0;
|
|
sb->data = nd;
|
|
sb->cap = new_cap;
|
|
return 1;
|
|
}
|
|
|
|
static int cj_sb_appendn(cj_strbuf *sb, const char *s, size_t n) {
|
|
if (!cj_strbuf_grow(sb, n)) return 0;
|
|
memcpy(sb->data + sb->len, s, n);
|
|
sb->len += n;
|
|
return 1;
|
|
}
|
|
|
|
static int cj_sb_appendc(cj_strbuf *sb, char c) {
|
|
return cj_sb_appendn(sb, &c, 1);
|
|
}
|
|
|
|
static int cj_sb_appends(cj_strbuf *sb, const char *s) {
|
|
return cj_sb_appendn(sb, s, strlen(s));
|
|
}
|
|
|
|
static int cj_append_str_escaped(cj_strbuf *sb, const char *s, size_t len) {
|
|
if (!cj_sb_appendc(sb, '"')) return 0;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
unsigned char c = (unsigned char)s[i];
|
|
switch (c) {
|
|
case '"': if (!cj_sb_appendn(sb, "\\\"", 2)) return 0; break;
|
|
case '\\': if (!cj_sb_appendn(sb, "\\\\", 2)) return 0; break;
|
|
case '\b': if (!cj_sb_appendn(sb, "\\b", 2)) return 0; break;
|
|
case '\f': if (!cj_sb_appendn(sb, "\\f", 2)) return 0; break;
|
|
case '\n': if (!cj_sb_appendn(sb, "\\n", 2)) return 0; break;
|
|
case '\r': if (!cj_sb_appendn(sb, "\\r", 2)) return 0; break;
|
|
case '\t': if (!cj_sb_appendn(sb, "\\t", 2)) return 0; break;
|
|
default:
|
|
if (c < 0x20u) {
|
|
char buf[8];
|
|
snprintf(buf, sizeof(buf), "\\u%04x", (unsigned int)c);
|
|
if (!cj_sb_appends(sb, buf)) return 0;
|
|
} else {
|
|
if (!cj_sb_appendc(sb, (char)c)) return 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return cj_sb_appendc(sb, '"');
|
|
}
|
|
|
|
static int cj_indent_line(cj_strbuf *sb, int indent, int depth) {
|
|
if (indent <= 0) return 1;
|
|
if (!cj_sb_appendc(sb, '\n')) return 0;
|
|
for (int i = 0; i < indent * depth; ++i)
|
|
if (!cj_sb_appendc(sb, ' ')) return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int cj_serialize(cj_strbuf *sb, const tinygltf_json *v,
|
|
int indent, int depth) {
|
|
/* Prevent C stack overflow on deeply nested JSON.
|
|
* Parser caps nesting at CJ_MAX_ITER; serializer uses the same limit. */
|
|
if (depth >= CJ_MAX_ITER) {
|
|
return cj_sb_appends(sb, "null");
|
|
}
|
|
switch (v->type_) {
|
|
case CJ_NULL:
|
|
return cj_sb_appends(sb, "null");
|
|
case CJ_BOOL:
|
|
return cj_sb_appends(sb, v->b_ ? "true" : "false");
|
|
case CJ_INT: {
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "%" PRId64, v->i_);
|
|
return cj_sb_appends(sb, buf);
|
|
}
|
|
case CJ_REAL: {
|
|
char buf[64];
|
|
double d = v->d_;
|
|
/* Non-finite values (NaN, Inf) cannot be represented in JSON.
|
|
* Detect by formatting first: nan/NaN starts with 'n'/'N'/'-n'/'-N',
|
|
* inf/Inf starts with 'i'/'I'/'-i'/'-I'. Output null for these. */
|
|
snprintf(buf, sizeof(buf), "%.17g", d);
|
|
{
|
|
const char *b = buf;
|
|
if (*b == '-') ++b;
|
|
if (*b == 'n' || *b == 'N' || *b == 'i' || *b == 'I')
|
|
return cj_sb_appends(sb, "null");
|
|
}
|
|
/* Ensure there's a decimal point so the value round-trips as float */
|
|
if (!strchr(buf, '.') && !strchr(buf, 'e') && !strchr(buf, 'E')) {
|
|
size_t bl = strlen(buf);
|
|
if (bl + 2 < sizeof(buf)) {
|
|
buf[bl] = '.';
|
|
buf[bl+1] = '0';
|
|
buf[bl+2] = '\0';
|
|
}
|
|
}
|
|
return cj_sb_appends(sb, buf);
|
|
}
|
|
case CJ_STRING: {
|
|
/* Defensive: if str_ is NULL (OOM during construction), use length 0.
|
|
* The invariant str_==NULL→str_len_==0 is enforced at all construction
|
|
* sites, but guard here in case of future callers. */
|
|
const char *s = v->str_ ? v->str_ : "";
|
|
size_t n = v->str_ ? v->str_len_ : 0u;
|
|
return cj_append_str_escaped(sb, s, n);
|
|
}
|
|
case CJ_ARRAY: {
|
|
if (!cj_sb_appendc(sb, '[')) return 0;
|
|
for (size_t i = 0; i < v->arr_size_; ++i) {
|
|
if (indent > 0 && !cj_indent_line(sb, indent, depth + 1)) return 0;
|
|
if (!cj_serialize(sb, &v->arr_data_[i], indent, depth+1)) return 0;
|
|
if (i + 1 < v->arr_size_ && !cj_sb_appendc(sb, ',')) return 0;
|
|
}
|
|
if (indent > 0 && v->arr_size_ > 0)
|
|
if (!cj_indent_line(sb, indent, depth)) return 0;
|
|
return cj_sb_appendc(sb, ']');
|
|
}
|
|
case CJ_OBJECT: {
|
|
if (!cj_sb_appendc(sb, '{')) return 0;
|
|
for (size_t i = 0; i < v->obj_size_; ++i) {
|
|
if (indent > 0 && !cj_indent_line(sb, indent, depth + 1)) return 0;
|
|
const tinygltf_json_member *m = &v->obj_data_[i];
|
|
/* Defensive: if key is NULL (OOM during insert), use length 0 */
|
|
const char *key = m->key ? m->key : "";
|
|
size_t keylen = m->key ? m->key_len : 0u;
|
|
if (!cj_append_str_escaped(sb, key, keylen)) return 0;
|
|
if (!cj_sb_appendc(sb, ':')) return 0;
|
|
if (indent > 0 && !cj_sb_appendc(sb, ' ')) return 0;
|
|
if (!cj_serialize(sb, &m->val, indent, depth + 1)) return 0;
|
|
if (i + 1 < v->obj_size_ && !cj_sb_appendc(sb, ',')) return 0;
|
|
}
|
|
if (indent > 0 && v->obj_size_ > 0)
|
|
if (!cj_indent_line(sb, indent, depth)) return 0;
|
|
return cj_sb_appendc(sb, '}');
|
|
}
|
|
default:
|
|
return cj_sb_appends(sb, "null");
|
|
}
|
|
}
|
|
|
|
/* ======================================================================
|
|
* tinygltf_json::dump() and ::parse() IMPLEMENTATIONS
|
|
* ====================================================================== */
|
|
|
|
inline std::string tinygltf_json::dump(int indent) const {
|
|
cj_strbuf sb;
|
|
if (!cj_strbuf_init(&sb, 4096)) return std::string();
|
|
cj_serialize(&sb, this, indent, 0);
|
|
std::string result(sb.data, sb.len);
|
|
cj_strbuf_free_data(&sb);
|
|
return result;
|
|
}
|
|
|
|
inline tinygltf_json tinygltf_json::parse(const char *first, const char *last,
|
|
std::nullptr_t,
|
|
bool allow_exceptions) {
|
|
cj_parse_ctx ctx;
|
|
ctx.cur = first;
|
|
ctx.end = last;
|
|
ctx.err = 0;
|
|
ctx.errmsg[0] = '\0';
|
|
|
|
tinygltf_json result;
|
|
cj_parse_json(&ctx, &result);
|
|
|
|
if (ctx.err) {
|
|
#ifndef TINYGLTF_JSON_NO_EXCEPTIONS
|
|
if (allow_exceptions) {
|
|
throw std::invalid_argument(
|
|
std::string("tinygltf_json::parse error: ") + ctx.errmsg);
|
|
}
|
|
#else
|
|
(void)allow_exceptions;
|
|
#endif
|
|
return tinygltf_json(); /* null on error */
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* ======================================================================
|
|
* TINYGLTF DETAIL NAMESPACE COMPATIBILITY
|
|
*
|
|
* These declarations make the custom JSON backend available as
|
|
* tinygltf::detail types/functions when TINYGLTF_USE_CUSTOM_JSON is set.
|
|
* ====================================================================== */
|
|
|
|
namespace tinygltf {
|
|
namespace detail {
|
|
|
|
using json = tinygltf_json;
|
|
using json_iterator = tinygltf_json::iterator;
|
|
using json_const_iterator = tinygltf_json::iterator;
|
|
using json_const_array_iterator = tinygltf_json::iterator;
|
|
using JsonDocument = tinygltf_json;
|
|
|
|
inline void JsonParse(JsonDocument &doc, const char *str, size_t length,
|
|
bool throwExc = false) {
|
|
doc = tinygltf_json::parse(str, str + length, nullptr, throwExc);
|
|
}
|
|
|
|
/* --- Type accessors --- */
|
|
|
|
inline bool GetInt(const json &o, int &val) {
|
|
if (o.is_number_integer() || o.is_number_unsigned()) {
|
|
val = o.get<int>();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool GetDouble(const json &o, double &val) {
|
|
if (o.is_number_float()) {
|
|
val = o.get<double>();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool GetNumber(const json &o, double &val) {
|
|
if (o.is_number()) {
|
|
val = o.get<double>();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool GetString(const json &o, std::string &val) {
|
|
if (o.is_string()) {
|
|
val = o.get<std::string>();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool IsArray(const json &o) { return o.is_array(); }
|
|
inline bool IsObject(const json &o) { return o.is_object(); }
|
|
inline bool IsEmpty(const json &o) { return o.empty(); }
|
|
|
|
inline json_const_array_iterator ArrayBegin(const json &o) {
|
|
return o.begin();
|
|
}
|
|
inline json_const_array_iterator ArrayEnd(const json &o) {
|
|
return o.end();
|
|
}
|
|
|
|
inline json_const_iterator ObjectBegin(const json &o) { return o.begin(); }
|
|
inline json_const_iterator ObjectEnd(const json &o) { return o.end(); }
|
|
inline json_iterator ObjectBegin(json &o) { return o.begin(); }
|
|
inline json_iterator ObjectEnd(json &o) { return o.end(); }
|
|
|
|
inline std::string GetKey(const json_const_iterator &it) { return it.key(); }
|
|
inline std::string GetKey(json_iterator &it) { return it.key(); }
|
|
|
|
inline const json &GetValue(const json_const_iterator &it) { return *it; }
|
|
inline json &GetValue(json_iterator &it) { return *it; }
|
|
|
|
inline bool FindMember(const json &o, const char *member,
|
|
json_const_iterator &it) {
|
|
it = o.find(member);
|
|
return it != o.end();
|
|
}
|
|
inline bool FindMember(json &o, const char *member, json_iterator &it) {
|
|
it = o.find(member);
|
|
return it != o.end();
|
|
}
|
|
|
|
inline void Erase(json &o, json_iterator &it) { o.erase(it); }
|
|
|
|
inline std::string JsonToString(const json &o, int spacing = -1) {
|
|
return o.dump(spacing);
|
|
}
|
|
|
|
/* --- Serialization helpers --- */
|
|
|
|
inline json JsonFromString(const char *s) { return json(s); }
|
|
|
|
inline void JsonAssign(json &dest, const json &src) { dest = src; }
|
|
|
|
inline void JsonAddMember(json &o, const char *key, json &&value) {
|
|
o[key] = static_cast<json &&>(value);
|
|
}
|
|
|
|
inline void JsonPushBack(json &o, json &&value) {
|
|
o.push_back(static_cast<json &&>(value));
|
|
}
|
|
|
|
inline bool JsonIsNull(const json &o) { return o.is_null(); }
|
|
|
|
inline void JsonSetObject(json &o) { o = json::object(); }
|
|
|
|
inline void JsonReserveArray(json &o, size_t /*s*/) {
|
|
o.set_array();
|
|
}
|
|
|
|
/* Stub allocator for RapidJSON-compatibility (not used by custom backend) */
|
|
struct CJ_NoAllocator {};
|
|
inline CJ_NoAllocator &GetAllocator() {
|
|
static CJ_NoAllocator alloc;
|
|
return alloc;
|
|
}
|
|
|
|
} /* namespace detail */
|
|
} /* namespace tinygltf */
|
|
|
|
#endif /* TINYGLTF_JSON_H_ */
|