Fighting with json - a war story
Source: Dev.to

Intro
I was refactoring the build environment for an internal C++ project at work. To keep things simple, we used the following JSON library:
https://github.com/nlohmann/json
The build system was based on CMake. I planned to replace our wrapper scripts for CMake with a preset.json. If you haven’t heard of this feature yet: when you want to set specific configurations for customers via CMake defines, you can predefine these in a dedicated preset file. It works surprisingly well. For my experimental render engine, I put all the Windows packages installation via vcpkg into a preset file like this:
{
"version": 3,
"configurePresets": [
{
"name": "default",
"binaryDir": "${sourceDir}",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "contrib/vcpkg/scripts/buildsystems/vcpkg.cmake"
}
}
]
}
You now invoke the build with:
cmake .\CMakeLists.txt --preset=default
under Windows, and the user no longer needs to worry about installing vcpkg, since it’s shipped under contrib.
I applied the same logic for compiler‑ and target‑specific settings in the company project. And suddenly, the build failed. The error message read:
error parsing version, missing ;
The file mentioned in the error (version) simply contained the current version string and wasn’t even used in the C++ build process at all — until now. So what happened?
What happened?
After some fruitless searching through the source code, trying to find out whether the version file had accidentally been included somewhere, I confirmed that this wasn’t the case. Otherwise, this build error would have appeared much earlier.
Next, I searched the CMake build environment to see if there was a pre‑processing step that might be turning the version file into a mis‑configured header file. Another dead end.
So what now? I identified the .cpp file that triggered the error. Unfortunately, the MSVC build output didn’t show which include caused the build to fail. I used the following compiler switch:
/showIncludes
With this switch enabled, all included files for the translation unit are shown. While digging through the generated output — which listed hundreds of includes — I noticed one header that seemed to be triggering the build error:
json/include/nlohmann/detail/macro_scope.hpp
Studying this header more closely revealed the culprit:
#ifdef __has_include
#if __has_include()
#include
#endif
#endif
If the preprocessor supports the __has_include feature, it checks whether a file named version exists. If it does, it gets included implicitly.
Why didn’t this happen in the old build setup? The answer is simple: in the new preset‑based build, the build directory was set to the root directory of the project.
In the old build, we used a directory structure like:
build//
I changed my CMake output directory to build. But that wasn’t enough. It had to be at least build/ — in other words, at least two directory levels deep to avoid the problem. That workaround fixed the issue.
What have I learned
- If build errors don’t make sense, enable all compiler options (e.g.,
/showIncludes) to extract more info from the build process. Without that option I would never have found the root cause. - If the result still seems illogical, accept that it’s happening and adjust your perspective.
- If I didn’t include the file, someone else must have. Implicit includes (like via
nlohmann) can introduce unexpected headers. - Just because you don’t know a preprocessor directive to include headers doesn’t mean it doesn’t exist.
- Visual Studio knows the
__has_includefeature — I didn’t. - Never make your build depend on the target directory structure.
I hate these kinds of bugs.
Thanks a lot for reading!