iguana, a C++ serialization engine that reflects over your structs
universal serialization engine
At a glance
- What is it?
- iguana is a C++20 and C++17 serialization library that turns a struct plus a one line metadata macro into JSON, XML, YAML, protobuf and binary output, with a C++26 path that reads members straight from the language reflection API. The design bet is that one reflection mechanism should feed every format.
- Who is it for?
- iguana suits a C++ codebase that serialises the same structs into several formats and does not want one schema language per output, since a single metadata declaration drives JSON, XML, YAML and protobuf.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 72 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One reflection, many output formats
iguana is described as a modern, universal and easy-to-use serialization engine developed in C++20 and C++17, and the repository title calls it a universal serialization engine based on compile-time reflection.
The motivation section states the goal plainly: serialize an object to any other format data with compile-time reflection, such as json, xml, binary, table and so on. The library is designed to unify and simplify serialization in a portable cross-platform manner, and to be easy to extend, so you can serialize any format of data with it.
The list of formats it names explicitly is serialization of json, of xml, of yaml, of protobuf, and of any customized format. Protobuf is worth pausing on, because a protobuf implementation usually implies a code generation step and a schema compiler, and iguana's claim is that the same struct metadata drives the generated protobuf schema as well.
The cross-platform claim is backed by a CI matrix in the README, with a badge for each configuration. Ubuntu 22.04 with clang 14.0.0, Ubuntu 22.04 with gcc 11.2.0, a GCC 16.1.0 container exercising the C++26 reflection path, macOS Monterey with AppleClang latest, and Windows Server 2022 with MSVC 19.33.31630.0. Five configurations, two of them covering the older compiler end of the range, which is the combination that matters for a library that has to build on what people actually have.
The project is licensed Apache-2.0, the default branch is master, the last push was on 2026-08-12, and the release tags are 1.2.0 from 2026-06-25, 1.1.0 from 2025-12-03 and 1.0.9 from 2025-05-08.
The metadata macro, and what it costs you
The way you use iguana is by defining metadata for your struct, and the mechanism depends on your compiler.
struct person
{
std::string name;
int age;
};
#if __cplusplus < 202002L
YLT_REFL(person, name, age) //define meta data
#endifThe condition is the point. If your compiler is a C++20 compiler, meaning gcc 11 or later, clang 13 or later, or MSVC 2022, you do not need to define YLT_REFL at all, because the library finds the members through the language's own facilities. Otherwise you need the macro.
That conditional is the whole design tension in the library, stated in one preprocessor block. A serialization engine that wants to reflect over members without the language's support has to fall back to a macro that lists the members, and that fallback is a maintenance obligation: rename a field and the macro has to change with it, and the compiler will not always tell you when you forget.
Once the metadata exists, the actual calls are minimal. Serializing is one call:
person p = { "tom", 28 };
iguana::string_stream ss; // here use std::string is also ok
iguana::to_json(p, ss);
std::cout << ss.str() << std::endl; which outputs {"name":"tom","age":28}. Deserialization is the mirror image, with iguana::from_json(p, json) on a std::string or a string_view.
The XML path is the same shape with to_xml and from_xml, and YAML with to_yaml and from_yaml. The tutorial's framing is that serializing an object to json is very simple, just need to call the to_json method, there is nothing more, and deserialization is equally simple.
There is also a DOM interface, iguana::jvalue, for when you want to parse before you know the type:
std::string_view str = R"(false)";
iguana::jvalue val;
iguana::parse(val, str.begin(), str.end());
std::error_code ec;
auto b = val.get<bool>(ec);The two overloads of get are a nice touch, one taking an error_code for the no-throw path and one that throws, so the choice is explicit at the call site rather than implied by a policy.
C++26 reflection, and the annotations that replace the macro
The C++26 path is the part that changes the design most, and it is the direction the project is heading.
With a C++26 static reflection compiler, iguana can read class members directly from the language reflection API. The CI job for that path uses the official gcc:16.1.0-trixie container with -std=gnu++26 -freflection, and configures CMake with -DENABLE_CXX26_REFLECTION=ON.
In that mode the metadata becomes language annotations rather than a macro:
struct [[= ylt::reflection::struct_name<"user">{}]] user_t {
[[= ylt::reflection::field_name<"id">{}]]
int user_id{};
std::string name;
[[= ylt::reflection::skip_field{}]]
int local_cache{};
};The annotations have specific meanings, and the README lists them. field_name changes the serialized field name for JSON, XML and YAML and for the generated protobuf schema. struct_name changes XML root names. skip_field excludes a member from reflection, which is the equivalent of a field you want in memory but not on the wire. skip_base excludes a base class from recursive member collection, so a derived struct does not inherit the base members into the output. And xml_required is the C++26 annotation form of the XML REQUIRED(type, fields...) macro.
The point of skip_field and skip_base is that they solve the problem the macro could not. With a macro you list the members you want, and a member you want to omit is simply not listed. With reflection the member set is discovered, so you need an explicit way to remove one, and that is what these annotations are.
The change notes for this path live in a dedicated file, iguana_reflect26_changes.md, and there is a Chinese version of the documentation, lang/iguana 使用文档.md, along with a struct_pb introduction document. The online discussion is on a Chinese community site, purecpp.cn, which tells you something about where the project's user base is.
Nested structures and containers, and the build
The library handles objects containing other objects and containers, and the metadata style is the same for each nested type.
struct one_t
{
int id;
};
YLT_REFL(one_t, id);
struct two
{
std::string name;
one_t one;
int age;
};
YLT_REFL(two, name, one, age);
struct composit_t
{
int a;
std::vector<std::string> b;
int c;
std::map<int, int> d;
std::unordered_map<int, int> e;
double f;
std::list<one_t> g;
};
YLT_REFL(composit_t, a, b, c, d, e, f, g);So a vector of strings, a map, an unordered map, a double and a list of a nested struct all go through the same to_json and from_json calls as the simple person struct. The nested struct gets its own metadata declaration, and the containing struct's metadata names it as a member.
The build is CMake, with a CMakeLists.txt at the root. The repository layout shows a core iguana/ directory, a test/ directory, an example/ directory with example.cpp and separate json, xml and yaml examples, a benchmark/ directory, a frozen/ directory, and two vendored pieces, rapidxml/ for the XML side and a data/ directory. There is also a lang/ directory for the translated documentation, a tool/ directory and a scripts/ directory.
The presence of rapidxml in the tree is worth noting. It suggests the XML support is built on an existing fast parser rather than a hand written one, which is the usual choice for a library that advertises speed.
The benchmark directory is a good sign for a performance oriented library, and so is the fact that the CI matrix runs the same code on five different compiler and operating system combinations. What the README does not claim is a benchmark result, so anyone relying on iguana's speed should measure it against their own structures rather than take the word for it.
Editorial conclusion
iguana suits a C++ codebase that serialises the same structs into several formats and does not want one schema language per output, since a single metadata declaration drives JSON, XML, YAML and protobuf. It is the wrong choice if you need code generation rather than reflection, because iguana works at compile time on the type and not on a schema file, and it is the wrong choice if you are on a toolchain without working C++20 concepts, since the YLT_REFL fallback exists precisely because the macro path needs gcc 11, clang 13 or MSVC 2022. Before adopting it, check the compiler matrix in the README, and if you are on C++26 read iguana_reflect26_changes.md, since the annotation form of the metadata is new and the change notes are where the behavioural differences are documented.
Frequently asked questions
How do I use iguana to serialize a struct to JSON?
Define the struct, and on a C++20 compiler no metadata macro is needed, otherwise define YLT_REFL(person, name, age). Then call iguana::to_json(p, ss) with a string_stream, or iguana::from_json(p, json) to read it back. The same shape applies to XML and YAML with to_xml, from_xml, to_yaml and from_yaml.
What does iguana require from the compiler?
C++20 and C++17 are both supported. The README says that if your compiler is a C++20 compiler, meaning gcc 11 or newer, clang 13 or newer or MSVC 2022, you do not need to define YLT_REFL, and otherwise you must define it in the YLT_REFL macro. The CI matrix covers Ubuntu with clang and gcc, a GCC 16.1.0 container for the C++26 path, macOS with AppleClang, and Windows Server 2022 with MSVC.
How does the C++26 reflection path work?
With a C++26 static reflection compiler, iguana reads class members directly from the language reflection API, and the metadata becomes annotations such as field_name, struct_name, skip_field and skip_base. The CI job for that path uses the gcc:16.1.0-trixie container with -std=gnu++26 -freflection and CMake option ENABLE_CXX26_REFLECTION=ON.
Can iguana generate protobuf?
The README lists serialization of protobuf among the formats the library provides, and the field_name annotation is documented as changing the serialized field name for JSON, XML and YAML and for the generated protobuf schema. There is a separate struct_pb introduction document in the lang/ directory.
Which containers and nested structures does iguana support?
The README example defines a struct containing a vector of strings, a map, an unordered map, a double and a list of a nested struct, and serializes it with the same to_json and from_json calls used for a simple struct. A nested struct needs its own YLT_REFL declaration, and the containing struct names it as a member.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/qicosmos-iguana)Was this useful?