JSON API | Vix.cpp Documentation
Vix.cpp v2.7.0 is here Read the blog
Skip to content

JSON API

The JSON API is used to build and parse structured data in Vix applications.

txt
API responses — request body parsing — objects — arrays — errors — sync payloads

Public headers

cpp
#include <vix.hpp>         // for HTTP apps (includes JSON)
#include <vix/json.hpp>    // for JSON-only code

Common namespace: vix::json. With using namespace vix: json::Json, json::obj(...), json::array(...), json::kv(...).

Basic JSON response

cpp
app.get("/health", [](Request &, Response &res){
  res.json({
    "ok", true,
    "service", "json-api"
  });
});

The main JSON type

vix::json::Json represents: object, array, string, number, boolean, null.

cpp
// Object
vix::json::Json data = vix::json::obj({
    {"ok", true},
    {"message", "Hello from Vix"}
});

// Array
vix::json::Json names = vix::json::array({"Alice", "Bob", "Charlie"});

// Dynamic array
vix::json::Json items = vix::json::Json::array();

items.push_back(vix::json::obj({
  {"id", 1},
  {"name", "Alice"}
}));

items.push_back(vix::json::obj({
  {"id", 2},
  {"name", "Bob"}
}));

Good response shapes

json
{ "ok": true, "data": {} }
{ "ok": true, "count": 2, "data": [] }
{ "ok": false, "error": "validation_failed", "message": "name is required" }

res.json(...)

cpp
res.json({
  "ok", true,
  "message", "Hello"
});

res.status(201).json({
  "ok", true,
  "message", "created"
});

res.status(400).json({
  "ok", false,
  "error", "bad_request"
});

Build JSON with helpers

cpp
struct User {
  std::int64_t id{};
  std::string name;
  std::string role;
};

static vix::json::Json user_to_json(const User &u)
{
  return vix::json::obj({
    {"id", u.id},
    {"name", u.name},
    {"role", u.role}
  });
}

static vix::json::Json users_to_json(const std::vector<User> &users)
{
  vix::json::Json items = vix::json::Json::array();
  for (const auto &u : users)
    items.push_back(user_to_json(u));
  return items;
}

// Usage in route
res.json({
  "ok", true,
  "count", static_cast<int>(users.size()),
  "data", users_to_json(users)
});

Read JSON request body

cpp
app.post("/users", [](Request &req, Response &res){
  const auto &body = req.json();

  // Always check shape first
  if (!body.is_object())
  {
    res.status(400).json({
      "ok", false,
      "error", "expected JSON object"
    });
    return;
  }

  // Read fields with fallback
  const std::string name = body.value("name", "");
  const std::string role = body.value("role", "user");

  if (name.empty())
  {
    res.status(400).json({
      "ok", false,
      "error", "name is required"
    });
    return;
  }

  res.status(201).json({
    "ok", true,
    "name", name,
    "role", role
  });
});

Error helper

cpp
static void respond_error(Response &res, int status,
                           const std::string &code, const std::string &message)
{
  res.status(status).json({
    "ok", false,
    "error", code,
    "message", message
  });
}

Stable error codes

txt
invalid_body, validation_failed, not_found, unauthorized, forbidden, conflict, internal_error

JSON and validation

cpp
const std::string email = body.value("email", "");

auto result = vix::validation::validate("email", email)
                  .required().email().result();

JSON answers: "what did the client send?" — Validation answers: "is this data valid?"

JSON and database

cpp
static json::Json user_row_to_json(const vix::db::Row &row)
{
  return json::obj({
    {"id", row.getInt64(0)},
    {"name", row.getString(1)},
    {"role", row.getString(2)}
  });
}

Flow: database row → C++ object → json::Json → res.json(...)

JSON and sync

cpp
// Payloads stored as JSON strings must be replayable
Operation op;
op.kind = "message.send";
op.payload = R"({"id":"msg_1","text":"hello","created_at":1710000000000})";

Complete example

cpp
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include <vix.hpp>
using namespace vix;

struct User { std::int64_t id{}; std::string name; std::string role; };

static json::Json user_to_json(const User &u)
{
  return json::obj({
    {"id", u.id},
    {"name", u.name},
    {"role", u.role}
  });
}

static json::Json users_to_json(const std::vector<User> &users)
{
  json::Json items = json::Json::array();
  for (const auto &u : users)
    items.push_back(user_to_json(u));
  return items;
}

static void respond_error(Response &res, int status,
                           const std::string &code, const std::string &message)
{
  res.status(status).json({
    "ok", false,
    "error", code,
    "message", message
  });
}

static std::optional<std::int64_t> parse_id(const std::string &text)
{
  try {
    return std::stoll(text);
  } catch (...) {
    return std::nullopt;
  }
}

int main()
{
  App app;
  std::vector<User> users{
    {1, "Alice", "admin"},
    {2, "Bob", "user"}
  };

  app.get("/health", [](Request &, Response &res) {
    res.json({"ok", true});
  });

  app.get("/users", [&users](Request &, Response &res){
    res.json({
      "ok", true,
      "count", static_cast<int>(users.size()),
      "data", users_to_json(users)
    });
  });

  app.get("/users/{id}", [&users](Request &req, Response &res){
    const auto id = parse_id(req.param("id"));
    if (!id) { respond_error(res, 400, "invalid_id", "Invalid user id"); return; }
    for (const auto &u : users)
      if (u.id == *id) {
        res.json({
          "ok", true,
          "data", user_to_json(u)
        });
        return;
      }
      respond_error(res, 404, "user_not_found", "User not found");
  });

  app.post("/users", [&users](Request &req, Response &res){
    const auto &body = req.json();
    if (!body.is_object()) {
      respond_error(res, 400, "invalid_body", "Expected JSON object");
      return;
    }

    const std::string name = body.value("name", "");
    if (name.empty()) {
      respond_error(res, 400, "validation_failed", "name is required");
      return;
    }

    const std::int64_t next_id = users.empty() ? 1 : users.back().id + 1;
    users.push_back({next_id, name, body.value("role", "user")});

    res.status(201).json({
      "ok", true,
      "message", "user created"
    });
  });

  app.run(8080);
  return 0;
}

Common mistakes

Returning different shapes everywhere

cpp
// Bad — inconsistent
{ "success": true }
{ "status": "ok", "items": [] }

// Good — predictable
{ "ok": true, "data": {} }

Reading fields without checking shape

cpp
// Wrong
const std::string name = body.value("name", "");

// Correct
if (!body.is_object()) {
  respond_error(res, 400, "invalid_body", "Expected JSON object");
  return;
}
const std::string name = body.value("name", "");

Building JSON strings manually

cpp
// Wrong
std::string body = "{\"ok\":true,\"name\":\"" + name + "\"}";

// Correct
res.json({
  "ok", true,
  "name", name
});

What you should remember

cpp
// success
res.json({
  "ok", true,
  "data", {}
});

// error
res.status(400).json({
  "ok", false,
  "error", "code"
});

const auto &body = req.json();
if (!body.is_object()) {
  /* error */
  return;
}

const std::string name = body.value("name", "");

The core idea: JSON is the public data shape of your API — keep it predictable.

Next: Middleware API

Released under the MIT License.