Parsing JSON at compile time with C++26 static reflection

Suppose that you have a configuration file in JSON. Something like this:

{ "width": 1920, "height": 1080, "fullscreen": true,
  "title": "My Game", "volume": 0.8 }

Normally you ship this file alongside your program, open it at startup, read it, and parse it. That is a lot of work for data that never changes. What if the file is fixed at build time? Could the compiler read it, parse it, and bake the result directly into the executable as a constant?

With C++26, the answer is yes. We need two new ingredients, all of which are usable right now with the latest version of the GCC compiler (16).

  1. #embed to pull the file into the program at compile time,
  2. A software library supporting static reflection like simdjson.

Let me show you how far we can take this.

The new #embed directive reads a file and expands it into a comma-separated list of byte values. To read the file data.json at compile time and keep it around as a constant, we write:

constexpr const char json_data[] = {
#embed "data.json"
    , 0
};

I use constexpr because I want the compiler to be allowed to inspect these bytes during constant evaluation. The trailing , 0 simply appends a null terminator, so the array can be treated as an ordinary C string.

There is no run-time input/output of any kind. The bytes are part of the program.

But embedded bytes are not yet useful by themselves. What I really want is a typed C++ object. In my example, the target type is this configuration struct:

struct Window {
  int         width;
  int         height;
  bool        fullscreen;
  std::string title;
  double      volume;
};

The traditional way to populate such a struct from JSON is to write, by hand, one line per field: read "width", store it into width, read "height", store it into height, and so on. It is tedious. And because it runs at startup, a malformed file becomes a run-time error, discovered by your users rather than by you.

Recent versions of simdjson can parse JSON at compile time using C++26 static reflection. The entry point is simdjson::compile_time::parse_json, and it does something I still find slightly magical: it reads the JSON and, from the keys it finds, and synthesises the struct type for you.

#define SIMDJSON_STATIC_REFLECTION 1
#include "simdjson.h"
constexpr const char json_data[] = {
#embed "data.json"
    , 0
};
constexpr auto window = simdjson::compile_time::parse_json<json_data>();

The variable window is a value computed entirely by the compiler. Its type is generated from the document: it has a width and a height (both 64-bit integers), a bool fullscreen, a double volume, and a title. From here on I write window.width and it behaves like any ordinary field.

How do I know the parsing really happened at compile time? Because I can assert things about the result that the compiler must check before the program even exists:

static_assert(window.width      == 1920);
static_assert(window.height     == 1080);
static_assert(window.fullscreen == true);

If I corrupt the JSON — delete a brace, misspell true, leave a trailing comma — the program no longer compiles, and the error points at the parse_json line. The broken file is caught at build time, on my machine, instead of at startup on someone else’s.

Because window is a genuine compile-time constant, any computation over it is a constant too. Consider this function:

int  screen_area()   { return window.width * window.height; }

Compiled with -O3, there is no multiplication, no field access, and certainly no parsing left — only the answers, as immediate values (here on my macBook):

screen_area:    mov  w0, #0xa400        // 0x1fa400 = 2073600
                movk w0, #0x1f, lsl #16
                ret

The JSON has vanished from the binary. It was read and parsed exactly once, by the compiler, and all that survives is the number 2073600.

Because static reflection is so new, when building with GCC 16, you need to pass the flags -std=c++26 -freflection: the -freflection flag is necessary to activate compile-time reflection You must also set the simdjson macro SIMDJSON_STATIC_REFLECTION=1 before importing the simdjson.h. It is a temporary safeguard.

The source code to reproduce these examples is available.

Reference: P2996 — Reflection for C++26 and the simdjson library.

Credit: The simdjson implementation is joint work with Francisco Geiman Thiesen.

Daniel Lemire, "Parsing JSON at compile time with C++26 static reflection," in Daniel Lemire's blog, June 14, 2026, https://lemire.me/blog/2026/06/14/parsing-json-at-compile-time-with-c26-static-reflection/.
[BibTeX]

Published by

Daniel Lemire

A computer science professor at the University of Quebec (TELUQ).

10 thoughts on “Parsing JSON at compile time with C++26 static reflection”

  1. At what point does this sort of… loop back on itself causing nothing to be accomplished? Would the json file be source code at that point? I guess it disappears as you said. But then what was the point of the json file in the first place?

      1. Sorry not trying to be dense (but might be failing). If you read a JSON config file at compile time then you can construct a bunch of types based on the JSON data at compile time. Which you could then pass those types to the other parts of your program/templates and what not. But, since this is happening at compile time, this seems no different than passing around a cpp/hpp file instead (configTypes.hpp). I don’t have much experience with compile time programming to be fair. Seems possibly redundant?

        1. This just an example but you could use it for embedding Tiled maps exported to json so you don’t parse them at runtime.

  2. This seems cool, but I have some questions (sorry if I’m being dense; I am very admittedly a newb programmer, and you are a prof of Comp Sci):

    This seems like it’s getting dangerously close to making c++ typeless – that is, how do you have meaningful type checking for a type that is just generated on the fly from the JSON file?

    What if something is wrong with the JSON file? Now the automatically generated type is wrong, but. . . there’s nothing really in your code to check that it’s wrong?

    I suppose to try to make this situation better, you could use a JSON Schema to first validate the JSON as part of your build system, and abort your build if it fails validation?

    Maybe I’m missing something, but I cringed at seeing the “auto” keyword in an object declaration. It’s one thing to use “auto” when you have a predefined type, and you are using it with the ‘new’ keyword, like the classic:

    auto myObject = new SomeDefinedClass();

    There, SomeDefinedClass is a class actually defined in your code somewhere, for which type enforcement can be done.

    But how do you do type enforcement for a completely compile-time generated type?

      1. Thanks. I will check out that link.

        Yes, basically, where the schema of a valid JSON doesn’t match the assumptions in your code.

        So for example, someone makes a typo and what should be “width” becomes “widt”. Or maybe they put a string where an integer is expected.

        Yeah, I would expect this would, in most cases, result in a compiler error. Except for when it doesn’t. What if instead of putting a string where an int was expected, they instead put a float value? 768.5

        That maybe wouldn’t result in a compiler error by default (but maybe would with static_assert ?), but then results in an error further down the line when you try to use that floating point value where an integer was declared in other code (like a method param).

        So you have an error – but it’s at the wrong spot. Where you see the error is no longer the location where the error actually is, but propagated to a different part of the code.

        1. Yes. You would catch this with a concept. That is what they are for.

          Observe that we already have this issue in C++. We write generic code like this:

          [cc lang=”cpp”]
          template
          void f(T t) {
          std::cout << t.name() << std::endl; // what if T.name() does not exist? } [/cc]

  3. I stumbled upon #embed because I recently learned that it has several parameters. I found it rather interesting that you add “,0” instead of using the suffix() parameter. However, I am also not sure what will become the agreed upon preferred syntax for this. Funilly enough, cppreference in its example for suffix() mixes your approach and the use of suffix by writing “suffix(,) 0”.

    One quick note on the question why one would use JSON instead of plain C++ for configuration: Sometimes we just embed a default configuration into our program, but the user can provide their own configuration. In this case it makes sense to just use a regular configuration file as the default configuration. No code dupliation or anything like that, but a single source of truth for the syntax of the configuration file.

Leave a Reply

Your email address will not be published.

You can also subscribe by email to this blog (non-commercial, no ads, weekly email).

How to post code (C, C++, Java, Python, etc.):

Wrap your code in backticks, like this:

`int main() {
    return 0;
}`