Build JSON
The JSON builders provide a compact way to create JSON objects and arrays in Vix.cpp code.
They are useful when the JSON value is created directly in C++, for example in API responses, configuration generation, tests, fixtures, metadata files, and small internal payloads.
The builders do not replace nlohmann::json. They are a thin convenience layer on top of it. The main JSON type remains vix::json::Json, which is an alias of nlohmann::json.
#include <vix/json.hpp>
using namespace vix::json;
int main()
{
Json user = o(
"id", 42,
"name", "Ada",
"active", true,
"skills", a("C++", "Networking", "Systems")
);
return 0;
}Header
For normal application code, include:
#include <vix/json.hpp>For direct usage of the builder API only, include:
#include <vix/json/build.hpp>The builder helpers live in:
namespace vix::jsonPublic API
| API | Purpose |
|---|---|
o(k1, v1, k2, v2, ...) | Build an ordered JSON object from alternating key/value arguments. |
a(v1, v2, ...) | Build a JSON array from values. |
kv({ {"key", Json(value)}, ...}) | Build a JSON object from a list of key/Json pairs. |
obj() | Create an empty JSON object. |
arr() | Create an empty JSON array. |
The most common helpers are o() and a().
Build an object with o()
Use o() when the object keys are known in the source code.
#include <vix/json.hpp>
#include <vix/print.hpp>
using namespace vix::json;
int main()
{
Json user = o(
"id", 42,
"name", "Ada",
"role", "admin"
);
vix::print(dumps_pretty(user));
return 0;
}Output shape:
{
"id": 42,
"name": "Ada",
"role": "admin"
}The function expects alternating key/value arguments:
auto value = o(
"key1", value1,
"key2", value2,
"key3", value3
);The number of arguments must be even. This is checked at compile time.
Valid:
auto user = o("name", "Ada", "age", 42);Invalid:
auto user = o("name", "Ada", "age");The invalid version has a key without a value.
Why o() returns OrderedJson
The o() helper returns OrderedJson.
using OrderedJson = nlohmann::ordered_json;This means object iteration and output follow insertion order. That is useful when the generated JSON is read by humans, compared in tests, or used in documentation examples.
auto payload = o(
"ok", true,
"id", 42,
"name", "Ada"
);The output keeps the same logical order:
{
"ok": true,
"id": 42,
"name": "Ada"
}This is not required by JSON itself, but it makes examples, configuration files, and generated payloads easier to inspect.
Build an array with a()
Use a() to build a JSON array from C++ values.
#include <vix/json.hpp>
#include <vix/print.hpp>
using namespace vix::json;
int main()
{
Json languages = a("C++", "Python", "JavaScript");
vix::print(dumps_pretty(languages));
return 0;
}Output shape:
["C++", "Python", "JavaScript"]The array can contain mixed JSON-compatible values:
Json values = a(
42,
true,
"Vix.cpp",
nullptr
);Output shape:
[42, true, "Vix.cpp", null]Build nested JSON
The builders can be nested.
#include <vix/json.hpp>
#include <vix/print.hpp>
using namespace vix::json;
int main()
{
Json user = o(
"id", 42,
"name", "Ada",
"profile", o(
"country", "UG",
"city", "Kampala"
),
"roles", a("admin", "editor")
);
vix::print(dumps_pretty(user));
return 0;
}Output shape:
{
"id": 42,
"name": "Ada",
"profile": {
"country": "UG",
"city": "Kampala"
},
"roles": ["admin", "editor"]
}This style is especially useful for API responses because the structure of the JSON remains visible in the C++ code.
Build an empty object
Use obj() when you want to start with an empty object and fill it progressively.
#include <vix/json.hpp>
#include <vix/print.hpp>
using namespace vix::json;
int main()
{
Json user = obj();
user["id"] = 42;
user["name"] = "Ada";
user["active"] = true;
vix::print(dumps_pretty(user));
return 0;
}Output shape:
{
"active": true,
"id": 42,
"name": "Ada"
}Use obj() when fields are conditional, computed in different parts of the function, or easier to assign one by one.
Build an empty array
Use arr() when you want to create an array and append values later.
#include <vix/json.hpp>
#include <vix/print.hpp>
using namespace vix::json;
int main()
{
Json items = arr();
items.push_back("first");
items.push_back("second");
items.push_back("third");
vix::print(dumps_pretty(items));
return 0;
}Output shape:
["first", "second", "third"]Use arr() when the number of elements is not known at the point where the array is created.
Build an object from pairs with kv()
Use kv() when the key/value pairs are already available as a list.
#include <vix/json.hpp>
#include <vix/print.hpp>
using namespace vix::json;
int main()
{
Json config = kv({
{"app", Json("Vix.cpp")},
{"debug", Json(true)},
{"port", Json(8080)}
});
vix::print(dumps_pretty(config));
return 0;
}Output shape:
{
"app": "Vix.cpp",
"debug": true,
"port": 8080
}The values passed to kv() must be Json values.
This is valid:
Json config = kv({
{"name", Json("Vix.cpp")},
{"port", Json(8080)}
});This is not the intended form:
Json config = kv({
{"name", "Vix.cpp"},
{"port", 8080}
});When the keys and values are known directly in the call site, prefer o():
Json config = o(
"name", "Vix.cpp",
"port", 8080
);Use kv() when a pair-based representation already exists or when the object is being assembled from data that is naturally stored as key/value entries.
Use builders in HTTP responses
In many Vix HTTP examples, JSON responses are written directly through res.json(...).
#include <vix.hpp>
using namespace vix;
int main()
{
App app;
app.get("/health", [](Request &, Response &res) {
res.json({
"ok", true,
"service", "api"
});
});
app.run();
return 0;
}For more complex payloads, the JSON builders can make the response easier to prepare before sending it.
#include <vix.hpp>
#include <vix/json.hpp>
using namespace vix;
int main()
{
App app;
app.get("/users/{id}", [](Request &req, Response &res) {
using namespace vix::json;
const std::string id = req.param("id");
Json payload = o(
"ok", true,
"user", o(
"id", id,
"name", "Ada"
),
"links", o(
"self", "/users/" + id
)
);
res.json(payload);
});
app.run();
return 0;
}The route still follows the normal Vix HTTP style: the handler receives Request &req and Response &res, then writes into res.
The JSON builder is only used to prepare the payload.
Use builders for configuration files
The builders are also useful when generating configuration or metadata files.
#include <vix/json.hpp>
using namespace vix::json;
int main()
{
Json config = o(
"server", o(
"host", "127.0.0.1",
"port", 8080
),
"log", o(
"level", "info",
"format", "kv"
)
);
dump_file("config/app.json", config);
return 0;
}Generated file shape:
{
"server": {
"host": "127.0.0.1",
"port": 8080
},
"log": {
"level": "info",
"format": "kv"
}
}For generated files, deterministic object order is useful because diffs remain easier to read.
Use builders in tests
Tests often need small JSON fixtures.
#include <vix/json.hpp>
using namespace vix::json;
Json make_user_fixture()
{
return o(
"id", 42,
"name", "Ada",
"active", true,
"roles", a("admin", "editor")
);
}This avoids long setup code and keeps the shape of the fixture visible.
When to use each builder
Use o() when the JSON object is known at the call site.
auto user = o("id", 42, "name", "Ada");Use a() when the JSON array is known at the call site.
auto roles = a("admin", "editor");Use obj() when the object is filled progressively.
Json user = obj();
user["id"] = 42;
user["name"] = "Ada";Use arr() when the array is filled progressively.
Json items = arr();
items.push_back("one");
items.push_back("two");Use kv() when you already have key/Json pairs.
Json data = kv({
{"name", Json("Ada")},
{"active", Json(true)}
});Common mistakes
Odd number of arguments in o()
This is invalid:
auto user = o("id", 42, "name");Every key must have a value:
auto user = o("id", 42, "name", "Ada");Passing non-key values as keys
Keys passed to o() must be convertible to std::string_view.
Good:
auto user = o("name", "Ada");Avoid using values that are not string-like as keys:
auto user = o(42, "Ada");Using kv() when o() is clearer
This works:
Json user = kv({
{"id", Json(42)},
{"name", Json("Ada")}
});But this is usually clearer:
Json user = o(
"id", 42,
"name", "Ada"
);Building very large JSON documents in one expression
For small and medium payloads, nested builders are readable.
For large documents, prefer a progressive structure:
Json root = obj();
root["users"] = arr();
for (const auto& user : users)
{
root["users"].push_back(o(
"id", user.id,
"name", user.name
));
}This keeps the code easier to debug and avoids deeply nested expressions.
Complete example
#include <vix/json.hpp>
#include <vix/print.hpp>
using namespace vix::json;
int main()
{
Json project = o(
"name", "Vix.cpp",
"kind", "runtime",
"modules", a(
o("name", "core", "enabled", true),
o("name", "json", "enabled", true),
o("name", "websocket", "enabled", true)
),
"metadata", o(
"language", "C++",
"format", "json"
)
);
vix::print(dumps_pretty(project));
return 0;
}Output shape:
{
"name": "Vix.cpp",
"kind": "runtime",
"modules": [
{
"name": "core",
"enabled": true
},
{
"name": "json",
"enabled": true
},
{
"name": "websocket",
"enabled": true
}
],
"metadata": {
"language": "C++",
"format": "json"
}
}Next steps
Continue with Parse JSON to learn how to parse JSON from strings and files.