Whether or not this is useful (I am also not sure), the JSON doesn't need to survive into runtime at all. If you include the JSON, do a bunch of `if constexpr`, and never touch the JSON outside of a constexpr context, it doesn't have to any footprint on your binary. (I'm not sure if #embed will force stuff to stay in your binary even if you never reference it at runtime)
You're right to point out that this is really 'first class JSON', rather than the Pydantic/Jackson type thing where the json barely exists and is immediately transformed into your models and classes.
Thanks for reading the article though, that's cool. I am a daw_json_link fan
Build up the test suite inside static asserts, or a macro that lets you switch. It will be really nice when you update right and your IDE will tell you before you even hit compile because clangd found an issue.
Yes, I agree. I don't see much practical use in this. I was just surprised how (relatively) straightforwards this is to do, and thought it was more cool than useful
Often I also find the opposite problem ... sure, you can do some stuff in (c++) metaprogramming, but can you (at compile time) generate a JSON/XML/YAML file that can be fed to some other part of the system?
The opposite 'toString' problem seems harder - I didn't try, but it should be possible now that std::string is constexpr.
I don't think you could parse it with, say, a class that has a std::string member (because of the transience restriction), but perhaps you can use lambdas that capture that string by reference, and call each other as appropriate?
As for exporting that as some sort of compiler artefact for use elsewhere, I am not sure how you would do that...
Hello! I wrote this short blog post about using pattern-matching-like template metaprogramming to deserialize JSON at build time - please let me know what you think (especially if you see improvements)
As someone who used to have to do this sort of compile-time stuff with previous versions of the standard, I'm jealous of how much more can be done now that I don't have to.
If you're looking for an interesting follow-up project, here's something I had to do once that's now become much easier: compute a compile-time hash of the compilation for the current translation unit, e.g. __BASE_FILE__ hashed together with __TIMESTAMP__ or the equivalents for each platform.
This allows you to dynamically invalidate on-disk caches and trigger new-build tripwires based on ongoing revisions. Development and release builds are handled identically: if source file X handles a cache and X was recompiled, discard the cache.
It's cute and neat to be able to do it 100% constexpr, however as you mention the indexers feels a tad inelegant.
I've written 2 iterations of a reflection library where you needed to annotate structs slightly with an ugly macro but once done you could just do: Message msg; if (parse_json(str,msg)) { ..process msg struct.. }
The previous iterations were for C++11 and C++17 but it seems that with C++20 features you don't even seem to need the macro uglyness so I personally think libraries need to move in the direction of plain old structs.
I recently actually tried to do a very similar thing, although a bit tighter in scope. What stopped me what that actually deserializing floating points cannot currently be done at compile time; the only utility available to do so is `from_chars` and it is only constexpr for ints.
I did not see any mention of this in the post; so are you actually simply extracting the string versions of the numbers, without verifying nor deserializing them?
The problem with this is that it will not actually parse double in IEEE 754, as you will accumulate inaccuracies at every step of the loop. In theory, when parsing a float, you are supposed to return the floating point that is closest to the original string number. Your code will not do that. Even if you accept the inaccuracy, if you for some reason load the JSON using a runtime library, you'll get different numbers and consequently and result that depend on those numbers. For my use-case this was not acceptable unfortunately..
Yes, very true. I noticed that even already at 3dp the floats start to compare unequal. The long double helped but it's not really.
I googled and found two examples of constexpr float parsing repositories, but from the sounds of things, you understand this problem better than I and will have seen them already
You can do something similar, no? std::pow is not constexpr (most float stuff is not, presumably due to floating point state) but you can implement 10^x anyway
template <StringLiteral str> static constexpr Key<str> key
in the class namespace - I think this this would work, but if I understand this correctly, You would need to do like
User user {...}; user[User::key<"myKey">]
Which actually is not so bad...