#include "foundation/mini_json.h" #include #include namespace joc::json { namespace { void skip_space(const std::string& text, std::size_t* index) { while (*index < text.size() && (text[*index] == ' ' || text[*index] == '\t' || text[*index] == '\n' || text[*index] == '\r')) { ++(*index); } } bool read_string(const std::string& text, std::size_t* index, std::string* out) { if (*index >= text.size() || text[*index] != '"') { return false; } ++(*index); out->clear(); while (*index < text.size()) { const char c = text[*index]; if (c == '\\') { if (*index + 1 >= text.size()) { return false; } const char escape = text[*index + 1]; *index += 2; switch (escape) { case '"': out->push_back('"'); break; case '\\': out->push_back('\\'); break; case '/': out->push_back('/'); break; case 'b': out->push_back('\b'); break; case 'f': out->push_back('\f'); break; case 'n': out->push_back('\n'); break; case 'r': out->push_back('\r'); break; case 't': out->push_back('\t'); break; case 'u': { if (*index + 4 > text.size()) { return false; } unsigned code = 0; for (int i = 0; i < 4; ++i) { const char digit = text[*index + static_cast(i)]; code <<= 4; if (digit >= '0' && digit <= '9') { code |= static_cast(digit - '0'); } else if (digit >= 'a' && digit <= 'f') { code |= static_cast(digit - 'a' + 10); } else if (digit >= 'A' && digit <= 'F') { code |= static_cast(digit - 'A' + 10); } else { return false; } } *index += 4; if (code < 0x80u) { out->push_back(static_cast(code)); } else if (code < 0x800u) { out->push_back(static_cast(0xC0u | (code >> 6))); out->push_back(static_cast(0x80u | (code & 0x3Fu))); } else { out->push_back(static_cast(0xE0u | (code >> 12))); out->push_back(static_cast(0x80u | ((code >> 6) & 0x3Fu))); out->push_back(static_cast(0x80u | (code & 0x3Fu))); } break; } default: return false; } continue; } if (c == '"') { ++(*index); return true; } out->push_back(c); ++(*index); } return false; } bool read_compound(const std::string& text, std::size_t* index, std::string* out) { const char open = text[*index]; const char close = open == '{' ? '}' : ']'; int depth = 0; const std::size_t start = *index; while (*index < text.size()) { const char c = text[*index]; if (c == '"') { std::string ignored; if (!read_string(text, index, &ignored)) { return false; } continue; } if (c == open) { ++depth; } else if (c == close) { --depth; if (depth == 0) { ++(*index); *out = text.substr(start, *index - start); return true; } } ++(*index); } return false; } } // namespace bool parse_object(const std::string& text, std::vector* out, std::string* error) { out->clear(); std::size_t index = 0; skip_space(text, &index); if (index >= text.size() || text[index] != '{') { if (error != nullptr) { *error = "metadata is not a JSON object"; } return false; } ++index; for (;;) { skip_space(text, &index); if (index < text.size() && text[index] == '}') { ++index; break; } if (index >= text.size() || text[index] == ',') { if (index >= text.size()) { if (error != nullptr) { *error = "unterminated JSON object"; } return false; } ++index; continue; } Member member; if (!read_string(text, &index, &member.key)) { if (error != nullptr) { *error = "expected a JSON key"; } return false; } skip_space(text, &index); if (index >= text.size() || text[index] != ':') { if (error != nullptr) { *error = "expected ':' after JSON key " + member.key; } return false; } ++index; skip_space(text, &index); if (index >= text.size()) { if (error != nullptr) { *error = "missing JSON value for " + member.key; } return false; } if (text[index] == '"') { member.is_string = true; if (!read_string(text, &index, &member.raw)) { if (error != nullptr) { *error = "bad JSON string for " + member.key; } return false; } } else if (text[index] == '{' || text[index] == '[') { if (!read_compound(text, &index, &member.raw)) { if (error != nullptr) { *error = "bad JSON container for " + member.key; } return false; } } else { const std::size_t start = index; while (index < text.size() && text[index] != ',' && text[index] != '}') { ++index; } member.raw = text.substr(start, index - start); while (!member.raw.empty() && (member.raw.back() == ' ' || member.raw.back() == '\n' || member.raw.back() == '\r' || member.raw.back() == '\t')) { member.raw.pop_back(); } } for (const Member& existing : *out) { if (existing.key == member.key) { if (error != nullptr) { *error = "duplicate JSON key " + member.key; } return false; } } out->push_back(std::move(member)); } return true; } const Member* find(const std::vector& members, const std::string& key) { for (const Member& member : members) { if (member.key == key) { return &member; } } return nullptr; } bool as_string(const Member& member, std::string* out) { if (!member.is_string || out == nullptr) { return false; } *out = member.raw; return true; } bool as_number(const Member& member, double* out) { if (member.is_string || out == nullptr) { return false; } char* end = nullptr; const double value = std::strtod(member.raw.c_str(), &end); if (end == member.raw.c_str() || !std::isfinite(value)) { return false; } *out = value; return true; } bool as_integer(const Member& member, long long* out) { double value = 0.0; if (!as_number(member, &value) || out == nullptr) { return false; } if (value != std::floor(value)) { return false; } *out = static_cast(value); return true; } } // namespace joc::json