JSON Validator — C++ source
Validate JSON and pinpoint errors with line and column numbers. Clear valid/invalid verdict plus the exact error location - runs entirely in your browser.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// json-validator — C++17 port: hand-rolled recursive-descent JSON validator, ported from src/lib/jsonValidate.ts.
#include <cstdio>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
struct Result {
bool valid;
std::string error; // empty when valid
std::optional<int> line, column; // nullopt = no position reported
};
class Validator {
public:
explicit Validator(std::string_view text) : text_(text) {}
Result validate() {
ws();
if (i_ >= text_.size()) return {false, "Input is empty", {}, {}};
bool ok = value();
if (ok) { ws(); ok = i_ >= text_.size(); } // reject trailing characters
if (!ok) {
auto [line, col] = lineCol(i_); // 1-based; "\r\n" counts as one break
return {false, "Unexpected token", line, col};
}
return {true, "", {}, {}};
}
private:
static constexpr std::string_view WS = " \t\r\n";
void ws() { while (i_ < text_.size() && WS.find(text_[i_]) != std::string_view::npos) ++i_; }
char peek() const { return i_ < text_.size() ? text_[i_] : '\0'; }
bool digit() const { return i_ < text_.size() && text_[i_] >= '0' && text_[i_] <= '9'; }
bool value() {
ws();
switch (peek()) {
case '{': return object(); case '[': return array(); case '"': return string();
case 't': return literal("true"); case 'f': return literal("false"); case 'n': return literal("null");
default: return number();
}
}
// Strings: any escape pair passes; a raw control char (< 0x20) fails.
bool string() {
++i_; // opening quote
while (i_ < text_.size() && text_[i_] != '"') {
if (text_[i_] == '\\') { if (++i_ >= text_.size()) return false; }
else if (static_cast<unsigned char>(text_[i_]) < 0x20) return false;
++i_;
}
return i_++ < text_.size(); // consume the closing quote, or fail at EOF
}
bool object() {
++i_; ws();
if (peek() == '}') { ++i_; return true; }
while (true) {
ws();
if (peek() != '"' || !string()) return false;
ws();
if (peek() != ':') return false;
++i_;
if (!value()) return false;
ws();
if (peek() == ',') { ++i_; continue; }
if (peek() == '}') { ++i_; return true; }
return false;
}
}
bool array() {
++i_; ws();
if (peek() == ']') { ++i_; return true; }
while (true) {
if (!value()) return false;
ws();
if (peek() == ',') { ++i_; continue; }
if (peek() == ']') { ++i_; return true; }
return false;
}
}
bool literal(std::string_view word) {
if (text_.substr(i_, word.size()) != word) return false;
i_ += word.size();
return true;
}
// RFC 8259 number: -?int(.frac)?([eE][+-]?exp)?
bool number() {
if (peek() == '-') ++i_;
if (!digit()) return false;
if (text_[i_] == '0') ++i_; else while (digit()) ++i_;
if (peek() == '.') { ++i_; if (!digit()) return false; while (digit()) ++i_; }
if (peek() == 'e' || peek() == 'E') {
++i_;
if (peek() == '+' || peek() == '-') ++i_;
if (!digit()) return false;
while (digit()) ++i_;
}
return true;
}
// 1-based line/column for a 0-based offset — the shared polyglot algorithm.
std::pair<int, int> lineCol(size_t offset) const {
int line = 1, col = 1;
for (size_t j = 0; j < offset && j < text_.size(); ++j) {
if (text_[j] == '\n') { ++line; col = 1; }
else if (text_[j] == '\r') { ++line; col = 1; if (j + 1 < text_.size() && text_[j + 1] == '\n') ++j; }
else ++col;
}
return {line, col};
}
std::string_view text_;
size_t i_ = 0;
};
int main() {
for (std::string_view sample : {"{\"a\": [1, 2, {\"b\": null}]}", "{\n \"a\": ,\n}"}) {
Result r = Validator(sample).validate();
if (r.valid) std::puts("valid");
else std::printf("invalid: %s (line %d, column %d)\n", r.error.c_str(), *r.line, *r.column);
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →