JSON Formatter — C++ source
Beautify, minify and validate JSON instantly. Paste, format, copy - all in your browser. Share a link to your exact input.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// json-formatter — parse, pretty-print (2-space indent), and minify.
//
// Language: C++ (C++17, standard library only — the STL ships no JSON)
// Source: CosmoDev polyglot showcase port of the `json-formatter` tool,
// ported from src/tools/JsonFormatter.tsx (the canonical
// TypeScript island).
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Mirroring the Rust port, this file includes a small hand-written
// recursive-descent parser and serializer. Object members are stored in a
// std::vector<std::pair<...>>, which preserves source insertion order just
// like a JavaScript engine.
//
// Parity caveats: numbers are held as double (matching JS Number), so
// integers beyond 2^53 lose precision, and printf %g switches to exponential
// notation at a different threshold than ECMAScript's Number::toString
// (e.g. 1e15 prints as "1e+15"). Non-ASCII bytes pass through untouched —
// the input is assumed to be UTF-8.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
// ---------------------------------------------------------------------
// Public surface — mirrors the TypeScript { output, error } shape.
// ---------------------------------------------------------------------
enum class Mode { Beautify, Minify };
struct FormatResult {
std::string output; // "" on empty input or parse error
std::optional<std::string> error; // engaged = parse failure
};
// A parsed JSON value. Object members live in a vector of pairs to preserve
// source insertion order just like a JavaScript engine.
struct JsonValue;
using JsonArray = std::vector<JsonValue>;
using JsonObject = std::vector<std::pair<std::string, JsonValue>>;
struct JsonValue {
std::variant<std::nullptr_t, bool, double, std::string, JsonArray, JsonObject> v;
};
// ---------------------------------------------------------------------
// Parser.
// ---------------------------------------------------------------------
// A parse failure: byte offset + message.
struct ParseError : std::runtime_error {
size_t offset;
ParseError(size_t off, const char* msg) : std::runtime_error(msg), offset(off) {}
};
class Parser {
public:
explicit Parser(std::string_view text) : text_(text) {}
JsonValue parseDocument() {
skipWs();
JsonValue value = parseValue();
skipWs();
if (pos_ < text_.size()) fail("unexpected trailing characters");
return value;
}
private:
[[noreturn]] void fail(const char* message) { throw ParseError(pos_, message); }
[[noreturn]] void failAt(size_t offset, const char* message) { throw ParseError(offset, message); }
char peek() const { return pos_ < text_.size() ? text_[pos_] : '\0'; }
void bump() { pos_++; }
static bool isDigit(char c) { return c >= '0' && c <= '9'; }
void skipWs() {
while (peek() == ' ' || peek() == '\t' || peek() == '\n' || peek() == '\r') bump();
}
// Match a literal keyword at the current position; on success advance.
bool matchKw(std::string_view kw) {
if (text_.substr(pos_, kw.size()) == kw) {
pos_ += kw.size();
return true;
}
return false;
}
JsonValue parseValue() {
const char c = peek();
if (c == '-' || isDigit(c)) return parseNumber();
switch (c) {
case '{': return parseObject();
case '[': return parseArray();
case '"': return JsonValue{parseString()};
case 't': case 'f': return parseBool();
case 'n': return parseNull();
default: fail("unexpected token");
}
}
JsonValue parseObject() {
bump(); // consume '{'
skipWs();
JsonObject members;
if (peek() == '}') {
bump();
return JsonValue{std::move(members)};
}
for (;;) {
skipWs();
if (peek() != '"') fail("expected string key");
std::string key = parseString();
skipWs();
if (peek() != ':') fail("expected ':' after key");
bump();
skipWs();
members.emplace_back(std::move(key), parseValue());
skipWs();
if (peek() == ',') { bump(); continue; }
if (peek() == '}') { bump(); break; }
fail("expected ',' or '}'");
}
return JsonValue{std::move(members)};
}
JsonValue parseArray() {
bump(); // consume '['
skipWs();
JsonArray items;
if (peek() == ']') {
bump();
return JsonValue{std::move(items)};
}
for (;;) {
skipWs();
items.push_back(parseValue());
skipWs();
if (peek() == ',') { bump(); continue; }
if (peek() == ']') { bump(); break; }
fail("expected ',' or ']'");
}
return JsonValue{std::move(items)};
}
std::string parseString() {
bump(); // opening '"'
std::string s;
for (;;) {
const char c = peek();
if (c == '\0') fail("unterminated string");
if (c == '"') { bump(); return s; }
if (c == '\\') {
bump();
parseEscape(s);
} else if (static_cast<unsigned char>(c) < 0x20) {
fail("unescaped control character in string");
} else {
s.push_back(c);
bump();
}
}
}
void parseEscape(std::string& s) {
switch (peek()) {
case '"': bump(); s.push_back('"'); return;
case '\\': bump(); s.push_back('\\'); return;
case '/': bump(); s.push_back('/'); return;
case 'b': bump(); s.push_back('\b'); return;
case 'f': bump(); s.push_back('\f'); return;
case 'n': bump(); s.push_back('\n'); return;
case 'r': bump(); s.push_back('\r'); return;
case 't': bump(); s.push_back('\t'); return;
case 'u': bump(); parseUnicodeEscape(s); return;
default: fail("invalid escape sequence");
}
}
// Enforce correct UTF-16 surrogate pairing for '\u' escapes.
void parseUnicodeEscape(std::string& s) {
const auto cp = parseHex4();
if (cp >= 0xD800 && cp <= 0xDBFF) {
if (peek() != '\\') fail("dangling high surrogate");
bump();
if (peek() != 'u') fail("expected '\\u' for surrogate pair");
bump();
const auto lo = parseHex4();
if (lo < 0xDC00 || lo > 0xDFFF)
fail("invalid low surrogate after high surrogate");
appendUtf8(s, 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00));
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
fail("unexpected low surrogate");
} else {
appendUtf8(s, cp);
}
}
// Read exactly four hexadecimal digits following a '\u'.
uint32_t parseHex4() {
uint32_t value = 0;
for (int i = 0; i < 4; i++) {
const char c = peek();
uint32_t d;
if (c >= '0' && c <= '9') d = static_cast<uint32_t>(c - '0');
else if (c >= 'a' && c <= 'f') d = static_cast<uint32_t>(c - 'a' + 10);
else if (c >= 'A' && c <= 'F') d = static_cast<uint32_t>(c - 'A' + 10);
else fail("invalid hex digit in '\\u' escape");
value = value * 16 + d;
bump();
}
return value;
}
static void appendUtf8(std::string& s, uint32_t cp) {
if (cp < 0x80) {
s.push_back(static_cast<char>(cp));
} else if (cp < 0x800) {
s.push_back(static_cast<char>(0xC0 | (cp >> 6)));
s.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else if (cp < 0x10000) {
s.push_back(static_cast<char>(0xE0 | (cp >> 12)));
s.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
s.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else {
s.push_back(static_cast<char>(0xF0 | (cp >> 18)));
s.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
s.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
s.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
}
}
JsonValue parseNumber() {
const size_t start = pos_;
if (peek() == '-') bump();
if (peek() == '0') {
bump();
} else if (peek() >= '1' && peek() <= '9') {
while (isDigit(peek())) bump();
} else {
failAt(start, "invalid number");
}
if (peek() == '.') {
bump();
if (!isDigit(peek())) fail("expected digit after decimal point");
while (isDigit(peek())) bump();
}
if (peek() == 'e' || peek() == 'E') {
bump();
if (peek() == '+' || peek() == '-') bump();
if (!isDigit(peek())) fail("expected digit in exponent");
while (isDigit(peek())) bump();
}
const std::string text{text_.substr(start, pos_ - start)};
try {
return JsonValue{std::stod(text)};
} catch (const std::exception&) {
fail("invalid number");
}
}
JsonValue parseBool() {
if (matchKw("true")) return JsonValue{true};
if (matchKw("false")) return JsonValue{false};
fail("invalid literal");
}
JsonValue parseNull() {
if (matchKw("null")) return JsonValue{nullptr};
fail("invalid literal");
}
std::string_view text_;
size_t pos_ = 0;
};
// ---------------------------------------------------------------------
// Serializer.
// ---------------------------------------------------------------------
// The classic C++17 "overloaded" visitor helper for std::visit.
template <class... Ts>
struct Overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;
void pushIndent(std::string& out, size_t depth, std::string_view pad) {
for (size_t i = 0; i < depth; i++) out.append(pad);
}
// Emit a JSON string literal, escaping per RFC 8259. Like JSON.stringify it
// leaves '/' unescaped and emits control characters below 0x20 as \uXXXX.
void writeJsonString(std::string& out, std::string_view s) {
out.push_back('"');
for (const char raw : s) {
const auto c = static_cast<unsigned char>(raw);
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
case '\b': out += "\\b"; break;
case '\f': out += "\\f"; break;
default:
if (c < 0x20) {
char esc[8];
std::snprintf(esc, sizeof esc, "\\u%04x", c);
out += esc;
} else {
out.push_back(raw);
}
}
}
out.push_back('"');
}
// Print a double the way JavaScript most often renders it: the shortest
// %g form that round-trips (try %.15g first, fall back to %.17g).
void writeNumber(std::string& out, double n) {
char tmp[40];
std::snprintf(tmp, sizeof tmp, "%.15g", n);
if (std::strtod(tmp, nullptr) != n)
std::snprintf(tmp, sizeof tmp, "%.17g", n);
out += tmp;
}
void writeCompact(const JsonValue& value, std::string& out) {
std::visit(Overloaded{
[&](std::nullptr_t) { out += "null"; },
[&](bool b) { out += b ? "true" : "false"; },
[&](double n) { writeNumber(out, n); },
[&](const std::string& s) { writeJsonString(out, s); },
[&](const JsonArray& items) {
out.push_back('[');
for (size_t i = 0; i < items.size(); i++) {
if (i > 0) out.push_back(',');
writeCompact(items[i], out);
}
out.push_back(']');
},
[&](const JsonObject& members) {
out.push_back('{');
for (size_t i = 0; i < members.size(); i++) {
if (i > 0) out.push_back(',');
writeJsonString(out, members[i].first);
out.push_back(':');
writeCompact(members[i].second, out);
}
out.push_back('}');
},
},
value.v);
}
void writePretty(const JsonValue& value, std::string& out, size_t depth, std::string_view pad) {
if (const auto* items = std::get_if<JsonArray>(&value.v); items && !items->empty()) {
out.push_back('[');
for (size_t i = 0; i < items->size(); i++) {
if (i > 0) out.push_back(',');
out.push_back('\n');
pushIndent(out, depth + 1, pad);
writePretty((*items)[i], out, depth + 1, pad);
}
out.push_back('\n');
pushIndent(out, depth, pad);
out.push_back(']');
} else if (const auto* members = std::get_if<JsonObject>(&value.v); members && !members->empty()) {
out.push_back('{');
for (size_t i = 0; i < members->size(); i++) {
if (i > 0) out.push_back(',');
out.push_back('\n');
pushIndent(out, depth + 1, pad);
writeJsonString(out, (*members)[i].first);
out += ": ";
writePretty((*members)[i].second, out, depth + 1, pad);
}
out.push_back('\n');
pushIndent(out, depth, pad);
out.push_back('}');
} else {
// Scalars and empty containers render identically in both modes.
writeCompact(value, out);
}
}
// ---------------------------------------------------------------------
// Entry point.
// ---------------------------------------------------------------------
// Parse and re-emit a JSON document the way the live island does. `indent`
// is the number of spaces per nesting level for Mode::Beautify (pass 2 to
// match the UI). Empty/whitespace-only input yields empty output, no error.
FormatResult formatJson(std::string_view input, Mode mode, int indent) {
constexpr const char* ws = " \t\r\n";
const size_t begin = input.find_first_not_of(ws);
if (begin == std::string_view::npos)
return FormatResult{.output = std::string{}};
const size_t end = input.find_last_not_of(ws);
const std::string_view raw = input.substr(begin, end - begin + 1);
try {
const JsonValue value = Parser(raw).parseDocument();
std::string pad(static_cast<size_t>(indent), ' ');
std::string out;
switch (mode) {
case Mode::Minify: writeCompact(value, out); break;
case Mode::Beautify: writePretty(value, out, 0, pad); break;
}
return FormatResult{.output = std::move(out)};
} catch (const ParseError& e) {
char where[32];
std::snprintf(where, sizeof where, " at byte %zu", e.offset);
return FormatResult{.output = std::string{}, .error = e.what() + std::string(where)};
}
}
int main() {
const std::string sample =
R"({"name":"CosmoDev","tools":["json","base64"],"free":true,"nested":{"ok":1}})";
const FormatResult result = formatJson(sample, Mode::Beautify, 2);
if (result.error)
std::printf("error: %s\n", result.error->c_str());
else
std::printf("%s\n", result.output.c_str());
return 0;
}
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 →