JSON to C++

Generate C++ structs from a JSON sample — nlohmann-style field mapping that keeps the JSON key as the field name wherever C++ allows it, std::optional where keys come and go or turn null, structs declared in dependency order, and includes emitted only when used. Free, no sign-up — runs locally in your browser.

Your work is auto-saved in this browser only, so a refresh won't lose it. Nothing is uploaded.

Why generate C++ structs from a sample?

C++ has no reflection to fall back on, so JSON work starts with the structs — and a real API response gives you plenty to transcribe: nested objects, arrays of objects with uneven fields, values that are sometimes null. With nlohmann/json the transcription has one elegant property: keep the field names equal to the JSON keys and the macro writes the mapping for you. This converter preserves exactly that property wherever C++ allows it, turns absence and null into std::optional, declares structs in the dependency order C++ demands, and emits only the includes the output actually uses. Where a key cannot be a field name — a keyword, a space, a dash — the field is renamed, marked, and reported, because that is the one place the macro stops working and you need to know.

How to use this tool

  1. Paste the JSON. A real API response is ideal; include several array elements if you have them, because absence across elements is how std::optional fields are discovered.
  2. Name the root struct. Root is fine for a quick look; for code you will keep, use the name it will wear in your project.
  3. Press Generate C++. Structs arrive in dependency order — nested types first, the root last.
  4. Read the notes if any appear. They list every nlohmann::json fallback, every renamed field that needs hand-written mapping, and every optional — nothing was decided silently.
  5. Copy or download the result, add NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE next to each struct (only for the ones whose field names all match their keys), and let the compiler take it from there.

The rules it follows

Numbers. Whole numbers become std::int64_t; any fraction widens the slot to double — a JSON 1.0 is the number 1 to every parser, so a slot reads as an integer unless a real fraction appears in the sample. Names. Structs are PascalCase after the key that holds them; fields keep the JSON key verbatim when it is a valid, non-keyword C++ identifier — most real-world keys qualify, and that fidelity is what makes the nlohmann macro work. Anything else is renamed, commented with its key, and noted. Absence and null. Missing from some elements or sometimes null → std::optional; null everywhere or genuinely mixed → nlohmann::json, always noted. Order and includes. Structs print in dependency order, and the include block lists only what the output uses — no <optional> when nothing is optional.

Worked example

Given this sample (the second user has no nickname):

{
  "teamName": "Platform",
  "users": [
    { "id": 1, "firstName": "Ada", "nickname": "ada" },
    { "id": 2, "firstName": "Grace" }
  ]
}

the converter emits:

#include <cstdint>
#include <optional>
#include <string>
#include <vector>

struct User {
    std::int64_t id;
    std::string firstName;
    std::optional<std::string> nickname;
};

struct Root {
    std::string teamName;
    std::vector<User> users;
};

User comes first because Root holds it by value; the std::optional is the second user talking — the one without a nickname. Field names match the JSON keys exactly, so both structs work with nlohmann's NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE as-is.

Frequently asked questions

Which JSON library are these structs written for?

nlohmann/json, the de-facto standard — the structs are plain aggregates whose field names equal the JSON keys, which is exactly what its NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE macro needs to generate to_json / from_json for you. That is also why field names keep the JSON key's spelling (camelCase and all) instead of being forced into snake_case: with this library, the field name IS the mapping. The macro itself is not printed — it belongs in your code next to each struct, and the page's worked example shows the shape it expects.

Why is a field wrapped in std::optional?

When the sample proves the value can fail to be there: the key is missing from some elements of an array, or it is present but sometimes null. std::optional<std::string> nick; reads exactly right in C++ — nullopt when the key is absent, a value when it is present — and nlohmann/json maps JSON null and a missing key onto it the way you would hope. A key that is null everywhere becomes nlohmann::json instead: there is no evidence what it would be an optional OF.

Why did one of my fields get renamed (class_ instead of class)?

Some JSON keys cannot be C++ identifiers as written: language keywords (class, template, namespace), keys with spaces or leading digits, keys with characters C++ names cannot contain. Those fields are snake_cased where needed, given a trailing underscore for keywords, and marked with a // JSON key comment. There is a cost, stated in the notes: nlohmann's macro maps field name to key name, so a renamed field no longer maps automatically — it needs hand-written to_json / from_json (or a manual mapping). The generator flags every such field rather than letting you discover it at runtime.

Why do the structs come out with the nested ones first?

Because C++ requires a type to be declared before another struct holds it by value — Root cannot contain a User member until User exists. So the output is in dependency order, root last. (The other generators on this site print the root first because their languages do not care; here the language does, and the output respects it.) If you split the structs across headers, keep that order in mind for your includes.

Why int64_t for integers, and why is there no uint64_t?

JSON numbers arrive as text and the sample can only prove 'whole number'. std::int64_t is the width nlohmann/json itself uses for signed integers, and it holds every integer a JSON document can realistically carry. Unsigned is a claim the sample cannot make — a value that happens to be positive in your sample may be a balance that goes negative tomorrow — so the generator stays signed and lets you narrow deliberately.

What does nlohmann::json as a field type mean?

That the sample could not settle the slot: it was null everywhere, or it held genuinely different types across elements. nlohmann::json is the library's own dynamic value type, so the field will parse — you just get no compile-time structure for it. Each such field is listed in the notes with its reason; feed the generator a more representative sample (or accept the dynamic slot) rather than expecting it to guess.

Related tools