Bitcoin full node and query server

Oct 28, 2025

View in context in the libbitcoin Slack #general mirror.

evoskuil21:05
So by annotating these objects for boost::json we get the streaming and non-streaming parse/serialize for free. This is what that looks like, using a couple of our libbitcoin-system macros to reduce the syntactic overhead:
namespace libbitcoin {
namespace network {
namespace json {

DECLARE_JSON_TAG_INVOKE(version);
DECLARE_JSON_TAG_INVOKE(value_t);
DECLARE_JSON_TAG_INVOKE(identity_t);
DECLARE_JSON_TAG_INVOKE(request_t);
DECLARE_JSON_TAG_INVOKE(response_t);

} // namespace json
} // namespace network
} // namespace libbitcoin
namespace libbitcoin {
namespace network {
namespace json {

// version
// ----------------------------------------------------------------------------

DEFINE_JSON_FROM_TAG(version)
{
    switch (instance)
    {
        case version::v1: value = "1.0"; break;
        case version::v2: value = "2.0"; break;
        default: value = {}; break;
    }
}

DEFINE_JSON_TO_TAG(version)
{
    if (value.is_string())
    {
        const auto& text = value.as_string();
        if (text == "1.0") return version::v1;
        if (text == "2.0") return version::v2;
    }

    return version::invalid;
}

// value_t
// ----------------------------------------------------------------------------

DEFINE_JSON_FROM_TAG(value_t)
{
    std::visit(overload
    {
        [&] (null_t) NOEXCEPT
        {
            value = nullptr;
        },
        [&](boolean_t visit) NOEXCEPT
        {
            value = visit;
        },
        [&](number_t visit) NOEXCEPT
        {
            value = visit;
        },
        [&](const string_t& visit) NOEXCEPT
        {
            value = visit;
        },
        [&](const array_t& visit) NOEXCEPT
        {
            value = value_from(visit);
        },
        [&](const object_t& visit) NOEXCEPT
        {
            value = value_from(visit);
        }
    }, instance.inner);
}

DEFINE_JSON_TO_TAG(value_t)
{
    if (value.is_null())
    {
        return { std::in_place_type<null_t> };
    }
    else if (value.is_bool())
    {
        return { std::in_place_type<boolean_t>, value.as_bool() };
    }
    else if (value.is_number())
    {
        return { std::in_place_type<number_t>, value.to_number<number_t>() };
    }
    else if (value.is_string())
    {
        return { std::in_place_type<string_t>, value.as_string() };
    }
    else if (value.is_array())
    {
        return { std::in_place_type<array_t>, value_to<array_t>(value) };
    }
    else if (value.is_object())
    {
        return { std::in_place_type<object_t>, value_to<object_t>(value) };
    }

    throw ostream_exception{ "value_t" };
}

// identity_t
// ----------------------------------------------------------------------------

DEFINE_JSON_FROM_TAG(identity_t)
{
    std::visit(overload
    {
        [&] (null_t) NOEXCEPT
        {
            value = boost::json::value{};
        },
        [&](code_t visit) NOEXCEPT
        {
            value = visit;
        },
        [&](const string_t& visit) NOEXCEPT
        {
            value = visit;
        }
    }, instance);
}

DEFINE_JSON_TO_TAG(identity_t)
{
    if (value.is_null())
    {
        return { null_t{} };
    }
    else if (value.is_number())
    {
        return { code_t{ value.to_number<code_t>() } };
    }
    else if (value.is_string())
    {
        return { string_t{ value.as_string() } };
    }

    throw ostream_exception{ "identity_t" };
}

// request_t
// ----------------------------------------------------------------------------

DEFINE_JSON_FROM_TAG(request_t)
{
    json_object object{};

    if (instance.jsonrpc != version::undefined)
    {
        object["jsonrpc"] = value_from(instance.jsonrpc);
    }

    if (instance.id.has_value())
    {
        object["id"] = value_from(instance.id.value());
    }

    if (!instance.method.empty())
    {
        object["method"] = value_from(instance.method);
    }

    if (instance.params.has_value())
    {
        if (const auto& params = instance.params.value();
            std::holds_alternative<array_t>(params))
        {
            object["params"] = value_from(std::get<array_t>(params));
        }
        else
        {
            object["params"] = value_from(std::get<object_t>(params));
        }
    }

    value = object;
}

// Bogus warnings on `it` not checked.
BC_PUSH_WARNING(NO_UNGUARDED_POINTERS)

DEFINE_JSON_TO_TAG(request_t)
{
    request_t request{};
    const auto& object = value.as_object();

    // jsonrpc
    if (const auto it = object.find("jsonrpc"); it != object.end())
    {
        request.jsonrpc = value_to<version>(it->value());
    }

    // method
    if (const auto it = object.find("method"); it != object.end())
    {
        request.method = it->value().as_string();
    }

    // id
    if (const auto it = object.find("id"); it != object.end())
    {
        request.id = value_to<identity_t>(it->value());
    }

    // params
    if (const auto it = object.find("params"); it != object.end())
    {
        if (const auto& params = it->value(); params.is_array())
        {
            request.params = params_t
            {
                std::in_place_type<array_t>, value_to<array_t>(params)
            };
        }
        else if (params.is_object())
        {
            request.params = params_t
            {
                std::in_place_type<object_t>, value_to<object_t>(params)
            };
        }
    }

    return request;
}

// response_t
// ----------------------------------------------------------------------------

DEFINE_JSON_FROM_TAG(response_t)
{
    json_object object{};

    if (instance.jsonrpc != version::undefined)
    {
        object["jsonrpc"] = value_from(instance.jsonrpc);
    }

    if (instance.id.has_value())
    {
        object["id"] = value_from(instance.id.value());
    }

    if (instance.error.has_value())
    {
        const auto& result = instance.error.value();

        object["error"] =
        {
            { "code", result.code },
            { "message", result.message }
        };

        if (result.data.has_value())
        {
            object["data"] = value_from(result.data.value());
        }
    }

    if (instance.result.has_value())
    {
        object["result"] = value_from(instance.result.value());
    }

    value = object;
}

DEFINE_JSON_TO_TAG(response_t)
{
    response_t response{};
    const auto& object = value.as_object();

    // jsonrpc
    if (const auto it = object.find("jsonrpc"); it != object.end())
    {
        response.jsonrpc = value_to<version>(it->value());
    }

    // id
    if (const auto it = object.find("id"); it != object.end())
    {
        if (const auto& id = it->value(); id.is_null())
        {
            response.id = { null_t{} };
        }
        else if (id.is_number())
        {
            response.id = { id.to_number<code_t>() };
        }
        else if (id.is_string())
        {
            response.id = { string_t{ id.as_string() } };
        }
    }

    // error
    if (const auto it = object.find("error"); it != object.end())
    {
        result_t result{};
        const auto& error = it->value().as_object();

        if (const auto code_it = error.find("code");
            code_it != error.end())
        {
            result.code = code_it->value().to_number<code_t>();
        }

        if (const auto message_it = error.find("message");
            message_it != error.end())
        {
            result.message = message_it->value().as_string();
        }

        if (const auto data_it = error.find("data");
            data_it != error.end())
        {
            result.data = value_to<value_option>(data_it->value());
        }

        response.error = result;
    }

    // result
    if (const auto it = object.find("result"); it != object.end())
    {
        response.result = value_to<value_option>(it->value());
    }

    return response;
}

BC_POP_WARNING()

} // namespace json
} // namespace network
} // namespace libbitcoin
(edited)