diff --git a/.gitignore b/.gitignore index 35eff11..78f3074 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,19 @@ tests/tester tests/tester_noexcept tests/issue-97.gltf tests/issue-261.gltf +tests/issue-495-external.gltf +# Test-generated output files (written by tester.cc during test run) +tests/Cube.gltf +tests/Cube.bin +tests/Cube.glb +tests/Cube_BaseColor.png +tests/Cube_MetallicRoughness.png +tests/Cube_with_embedded_images.gltf +tests/Cube_with_image_files.gltf +tests/tmp.glb +tests/ issue-236.gltf +tests/ issue-236.bin +tests/ 2x2 image has multiple spaces.png # unignore !Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index ab2527a..b6f51e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ option(TINYGLTF_BUILD_TESTS "Build unit tests" OFF) option(TINYGLTF_HEADER_ONLY "On: header-only mode. Off: create tinygltf library(No TINYGLTF_IMPLEMENTATION required in your project)" OFF) option(TINYGLTF_INSTALL "Install tinygltf files during install step. Usually set to OFF if you include tinygltf through add_subdirectory()" ON) option(TINYGLTF_INSTALL_VENDOR "Install vendored nlohmann/json and nothings/stb headers" ON) +option(TINYGLTF_USE_CUSTOM_JSON "Use the built-in fast JSON parser (tinygltf_json.h) instead of nlohmann/json" OFF) if (TINYGLTF_BUILD_LOADER_EXAMPLE) add_executable(loader_example @@ -46,6 +47,15 @@ if (TINYGLTF_BUILD_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/tests ) add_test(NAME tester COMMAND tester WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) + + # Build and run tests with the custom JSON backend enabled to catch regressions + add_executable(tester_customjson tests/tester.cc) + target_include_directories(tester_customjson PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/tests + ) + target_compile_definitions(tester_customjson PRIVATE TINYGLTF_USE_CUSTOM_JSON) + add_test(NAME tester_customjson COMMAND tester_customjson WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests) endif (TINYGLTF_BUILD_TESTS) # @@ -71,6 +81,14 @@ else (TINYGLTF_HEADER_ONLY) ) endif (TINYGLTF_HEADER_ONLY) +if (TINYGLTF_USE_CUSTOM_JSON) + if (TINYGLTF_HEADER_ONLY) + target_compile_definitions(tinygltf INTERFACE TINYGLTF_USE_CUSTOM_JSON) + else () + target_compile_definitions(tinygltf PUBLIC TINYGLTF_USE_CUSTOM_JSON) + endif () +endif () + if (TINYGLTF_INSTALL) install(TARGETS tinygltf EXPORT tinygltfTargets) install(EXPORT tinygltfTargets NAMESPACE tinygltf:: FILE TinyGLTFTargets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/tinygltf) @@ -80,6 +98,7 @@ if (TINYGLTF_INSTALL) INSTALL ( FILES tiny_gltf.h + tinygltf_json.h ${TINYGLTF_EXTRA_SOUECES} DESTINATION include diff --git a/tiny_gltf.h b/tiny_gltf.h index 7e62630..509640e 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -1711,7 +1711,11 @@ class TinyGLTF { #endif // __GNUC__ #ifndef TINYGLTF_NO_INCLUDE_JSON -#ifndef TINYGLTF_USE_RAPIDJSON +#ifdef TINYGLTF_USE_CUSTOM_JSON +#ifndef TINYGLTF_NO_INCLUDE_CUSTOM_JSON +#include "tinygltf_json.h" +#endif +#elif !defined(TINYGLTF_USE_RAPIDJSON) #include "json.hpp" #else #ifndef TINYGLTF_NO_INCLUDE_RAPIDJSON @@ -1799,7 +1803,10 @@ class TinyGLTF { namespace tinygltf { namespace detail { -#ifdef TINYGLTF_USE_RAPIDJSON +#ifdef TINYGLTF_USE_CUSTOM_JSON +// Types and JsonParse are provided by tinygltf_json.h (already included above) +// via 'namespace tinygltf { namespace detail { ... } }' declarations. +#elif defined(TINYGLTF_USE_RAPIDJSON) #ifdef TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR // This uses the RapidJSON CRTAllocator. It is thread safe and multiple @@ -1871,6 +1878,7 @@ using json_const_array_iterator = json_const_iterator; using JsonDocument = json; #endif +#ifndef TINYGLTF_USE_CUSTOM_JSON void JsonParse(JsonDocument &doc, const char *str, size_t length, bool throwExc = false) { #ifdef TINYGLTF_USE_RAPIDJSON @@ -1880,6 +1888,7 @@ void JsonParse(JsonDocument &doc, const char *str, size_t length, doc = detail::json::parse(str, str + length, nullptr, throwExc); #endif } +#endif // !TINYGLTF_USE_CUSTOM_JSON } // namespace detail } // namespace tinygltf @@ -3436,6 +3445,7 @@ bool DecodeDataURI(std::vector *out, std::string &mime_type, return true; } +#ifndef TINYGLTF_USE_CUSTOM_JSON namespace detail { bool GetInt(const detail::json &o, int &val) { #ifdef TINYGLTF_USE_RAPIDJSON @@ -3659,6 +3669,7 @@ std::string JsonToString(const detail::json &o, int spacing = -1) { } } // namespace detail +#endif // !TINYGLTF_USE_CUSTOM_JSON static bool ParseJsonAsValue(Value *ret, const detail::json &o) { Value val{}; @@ -6962,6 +6973,7 @@ bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err, /////////////////////// // GLTF Serialization /////////////////////// +#ifndef TINYGLTF_USE_CUSTOM_JSON namespace detail { detail::json JsonFromString(const char *s) { #ifdef TINYGLTF_USE_RAPIDJSON @@ -7034,6 +7046,7 @@ void JsonReserveArray(detail::json &o, size_t s) { (void)(s); } } // namespace detail +#endif // !TINYGLTF_USE_CUSTOM_JSON // typedef std::pair json_object_pair; diff --git a/tinygltf_json.h b/tinygltf_json.h new file mode 100644 index 0000000..f6a0203 --- /dev/null +++ b/tinygltf_json.h @@ -0,0 +1,1907 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +/* C++ headers (minimal) */ +#include +#include /* for std::nullptr_t */ +#include /* 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 +#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 +#elif defined(TINYGLTF_JSON_SIMD_SSE2) +# include +#endif + +#ifdef TINYGLTF_JSON_SIMD_NEON +# include +#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 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(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(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(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(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(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(this); + return self->begin(); +} +inline tinygltf_json::iterator tinygltf_json::end() const { + tinygltf_json *self = const_cast(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(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(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() specializations + * ====================================================================== */ + +template<> inline double tinygltf_json::get() const { + if (type_ == CJ_REAL) return d_; + if (type_ == CJ_INT) return (double)i_; + return 0.0; +} + +template<> inline int tinygltf_json::get() const { + if (type_ == CJ_INT) return (int)i_; + if (type_ == CJ_REAL) return (int)d_; + return 0; +} + +template<> inline int64_t tinygltf_json::get() const { + if (type_ == CJ_INT) return i_; + if (type_ == CJ_REAL) return (int64_t)d_; + return 0; +} + +template<> inline uint64_t tinygltf_json::get() 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() const { + if (type_ == CJ_BOOL) return b_ != 0; + return false; +} + +template<> inline std::string tinygltf_json::get() 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 +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(i_); + } + if (type_ == CJ_REAL) return static_cast(d_); + if (type_ == CJ_BOOL) return static_cast(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(); + return true; + } + return false; +} + +inline bool GetDouble(const json &o, double &val) { + if (o.is_number_float()) { + val = o.get(); + return true; + } + return false; +} + +inline bool GetNumber(const json &o, double &val) { + if (o.is_number()) { + val = o.get(); + return true; + } + return false; +} + +inline bool GetString(const json &o, std::string &val) { + if (o.is_string()) { + val = o.get(); + 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(value); +} + +inline void JsonPushBack(json &o, json &&value) { + o.push_back(static_cast(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_ */