nlohmann/json
A complete, runnable example of teaching Burl a body type it has never heard of,
extracted from example/nlohmann_json.cpp. It puts
Extending to work on
nlohmann::json: a pair of tag_invoke
overloads, found by argument-dependent lookup, that make the type a first-class
body in both directions.
Sending: body_from_tag
The body_from_tag<T> overload serializes the document up front with
dump(), so the body owns the text and reports a known Content-Length.
burl::any_request_body
tag_invoke(burl::body_from_tag<nlohmann::json>, const nlohmann::json& value)
{
class json_body
{
std::string text_;
public:
explicit json_body(const nlohmann::json& value)
: text_(value.dump())
{
}
std::optional<std::string>
content_type() const
{
return "application/json";
}
std::optional<std::uint64_t>
content_length() const noexcept
{
return text_.size();
}
capy::io_task<>
write(capy::any_buffer_sink& sink) const
{
auto [ec, n] = co_await sink.write(capy::make_buffer(text_));
co_return { ec };
}
};
return json_body{ value };
}
See Extending.
Receiving: body_to_tag
The body_to_tag<T> overload reads the body and parses it, following the
recommended try_as_view pattern: in place when the
body fits the parser’s buffer, falling back to a std::string on overflow.
capy::io_task<nlohmann::json>
tag_invoke(burl::body_to_tag<nlohmann::json>, burl::response& resp)
{
// Try the parser's in-place buffer first; it is allocation-free
// when the body fits.
auto [ec, sv] = co_await resp.try_as_view();
// Fall back to a heap string when the body is larger than the buffer.
std::string st;
if(ec == boost::http::error::in_place_overflow)
{
auto [sec, body] = co_await resp.try_as<std::string>();
ec = sec;
st = std::move(body);
sv = st;
}
if(ec)
co_return { ec, {} };
// Surface a parse failure as an error rather than a discarded value.
auto doc = nlohmann::json::parse(sv, nullptr, false);
if(doc.is_discarded())
co_return { make_error_code(std::errc::bad_message), {} };
co_return { {}, std::move(doc) };
}
See Extending.
Using It
With both overloads in place, nlohmann::json is just another body type: the
same body and as<T> calls
that drive the built-ins now work on it unchanged:
nlohmann::json body({ { "user", "John" }, { "lang", "En" } });
auto r1 = co_await client.post("https://postman-echo.com/post")
.body(body)
.as<nlohmann::json>();
std::cout << r1.dump(4) << '\n';
When the value is a literal, you can name the type on
body<T> to construct it inline:
auto r2 = co_await client.post("https://postman-echo.com/post")
.body<nlohmann::json>({ 1, 2, 3 })
.as<nlohmann::json>();
std::cout << r2.dump(4) << '\n';
Next Steps
-
Extending — The two
tag_invokeoverloads in depth, including forwarding extra arguments -
Request Bodies — The built-in body types and how framing is chosen
-
Responses — The built-in conversions and
try_as_view