JSON to Zod Schema — C++ source
Generate Zod validation schemas from JSON. Infers z.string, z.number, z.boolean, z.object, z.array, z.null, and z.union for mixed arrays.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// JSON -> Zod schema inference — C++ (C++20) port of the json-to-zod tool:
// the recursive InferZod walk from jsonToZod.ts / go.go. The stdlib has no
// JSON engine, so the demo builds the value tree from variant nodes; object
// fields are vector<pair> so textual key order survives.
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include <variant>
#include <vector>
struct JVal;
using JArr = std::vector<JVal>;
using JObj = std::vector<std::pair<std::string, JVal>>; // key order preserved
struct JVal {
std::variant<std::nullptr_t, bool, double, std::string, JArr, JObj> v;
};
static JVal jstr(const char *s) { return JVal{std::string(s)}; }
static JVal jnum(double d) { return JVal{d}; }
static JVal jbool(bool b) { return JVal{b}; }
static JVal jarr(JArr a) { return JVal{std::move(a)}; }
static JVal jobj(JObj o) { return JVal{std::move(o)}; }
static std::string pad(int n) { return std::string(static_cast<size_t>(n), ' '); }
// Infer one element schema; holds_alternative dispatch mirrors the TS switch.
static std::string infer(const JVal &val, int indent) {
const auto &x = val.v;
if (std::holds_alternative<std::nullptr_t>(x)) return "z.null()";
if (std::holds_alternative<bool>(x)) return "z.boolean()";
if (std::holds_alternative<double>(x)) return "z.number()";
if (std::holds_alternative<std::string>(x)) return "z.string()";
if (const auto *arr = std::get_if<JArr>(&x)) {
if (arr->empty()) return "z.array(z.unknown())";
std::vector<std::string> types;
for (const JVal &e : *arr) types.push_back(infer(e, indent + 2));
bool same = true; // one shared element type
for (const auto &t : types) if (t != types[0]) same = false;
if (same) return "z.array(" + types[0] + ")";
std::string joined; // union of EVERY element
for (size_t i = 0; i < types.size(); ++i)
joined += types[i] + (i + 1 < types.size() ? ",\n" : "");
std::string out = "z.array(z.union([\n";
std::istringstream lines{joined};
for (std::string line; std::getline(lines, line);) // pad EVERY line
out += pad(indent + 2) + line + "\n";
return out + "]))"; // close unpadded — pad("", indent) is "" in TS
}
const auto *obj = std::get_if<JObj>(&x);
if (obj->empty()) return "z.object({})";
std::string out = "z.object({\n"; // fields in textual order
for (const auto &[key, fval] : *obj)
out += pad(indent + 2) + key + ": " + infer(fval, indent + 2) + ",\n";
return out + pad(indent) + "})";
}
int main() {
// {"name": "Ada", "admin": true, "meta": {"roles": ["admin", 1]}}
JVal doc = jobj({
{"name", jstr("Ada")},
{"admin", jbool(true)},
{"meta", jobj({{"roles", jarr({jstr("admin"), jnum(1)})}})},
});
std::cout << "const User = " << infer(doc, 2) << ";\n";
}
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 →