Skip to content

base/json_utility.cpp

Namespaces

Name
rlp
rlp::base
rlp::base::json

Functions

Name
const char * to_string(JsonErrorCode code)
JsonResult< boost::json::object > parse_object(std::string_view json_text)
JsonResult< const boost::json::value * > get_value(const boost::json::object & object, std::string_view field_name)
JsonResult< std::string > get_string(const boost::json::object & object, std::string_view field_name)
JsonResult< std::string > parse_string(const boost::json::value & value)
JsonResult< std::string > parse_string(const boost::json::value & value, std::string_view field_name)
JsonResult< std::string > get_optional_string(const boost::json::object & object, std::string_view field_name, std::string default_value)
JsonResult< bool > get_bool(const boost::json::object & object, std::string_view field_name)
JsonResult< bool > parse_bool(const boost::json::value & value)
JsonResult< bool > parse_bool(const boost::json::value & value, std::string_view field_name)
JsonResult< const boost::json::array * > get_array(const boost::json::object & object, std::string_view field_name)
JsonResult< uint8_t > get_u8(const boost::json::object & object, std::string_view field_name)
JsonResult< uint32_t > get_u32(const boost::json::object & object, std::string_view field_name)
JsonResult< uint64_t > get_u64(const boost::json::object & object, std::string_view field_name)
JsonResult< size_t > parse_size_t(const boost::json::value & value)
JsonResult< uint8_t > parse_u8(const boost::json::value & value)
JsonResult< uint8_t > parse_u8(const boost::json::value & value, std::string_view field_name)
JsonResult< uint32_t > parse_u32(const boost::json::value & value)
JsonResult< uint32_t > parse_u32(const boost::json::value & value, std::string_view field_name)
JsonResult< uint64_t > parse_u64(const boost::json::value & value)
JsonResult< uint64_t > parse_u64(const boost::json::value & value, std::string_view field_name)
JsonResult< JsonParsedObject > parse_schema_object(std::string_view json_text, const JsonSchemaObject & schema)
JsonResult< JsonParsedObject > parse_schema_object(const boost::json::object & object, const JsonSchemaObject & schema)
JsonResult< JsonParsedValue > parse_schema_value(const boost::json::value & value, JsonFieldType type, const JsonSchemaObject * object_schema, const JsonSchemaArray * array_schema, std::string_view field_path)
JsonResult< const JsonParsedValue * > get_parsed_value(const JsonParsedObject & object, std::string_view field_name)
JsonResult< std::string > get_parsed_string(const JsonParsedObject & object, std::string_view field_name)
JsonResult< bool > get_parsed_bool(const JsonParsedObject & object, std::string_view field_name)
JsonResult< uint8_t > get_parsed_u8(const JsonParsedObject & object, std::string_view field_name)
JsonResult< uint32_t > get_parsed_u32(const JsonParsedObject & object, std::string_view field_name)
JsonResult< uint64_t > get_parsed_u64(const JsonParsedObject & object, std::string_view field_name)
JsonResult< size_t > get_parsed_size(const JsonParsedObject & object, std::string_view field_name)
JsonResult< const JsonParsedArray * > get_parsed_array(const JsonParsedObject & object, std::string_view field_name)
JsonResult< const JsonParsedObject * > get_parsed_object(const JsonParsedObject & object, std::string_view field_name)

Functions Documentation

function to_string

const char * to_string(
    JsonErrorCode code
)

function parse_object

JsonResult< boost::json::object > parse_object(
    std::string_view json_text
)

function get_value

JsonResult< const boost::json::value * > get_value(
    const boost::json::object & object,
    std::string_view field_name
)

function get_string

JsonResult< std::string > get_string(
    const boost::json::object & object,
    std::string_view field_name
)

function parse_string

JsonResult< std::string > parse_string(
    const boost::json::value & value
)

function parse_string

JsonResult< std::string > parse_string(
    const boost::json::value & value,
    std::string_view field_name
)

function get_optional_string

JsonResult< std::string > get_optional_string(
    const boost::json::object & object,
    std::string_view field_name,
    std::string default_value
)

function get_bool

JsonResult< bool > get_bool(
    const boost::json::object & object,
    std::string_view field_name
)

function parse_bool

JsonResult< bool > parse_bool(
    const boost::json::value & value
)

function parse_bool

JsonResult< bool > parse_bool(
    const boost::json::value & value,
    std::string_view field_name
)

function get_array

JsonResult< const boost::json::array * > get_array(
    const boost::json::object & object,
    std::string_view field_name
)

function get_u8

JsonResult< uint8_t > get_u8(
    const boost::json::object & object,
    std::string_view field_name
)

function get_u32

JsonResult< uint32_t > get_u32(
    const boost::json::object & object,
    std::string_view field_name
)

function get_u64

JsonResult< uint64_t > get_u64(
    const boost::json::object & object,
    std::string_view field_name
)

function parse_size_t

JsonResult< size_t > parse_size_t(
    const boost::json::value & value
)

function parse_u8

JsonResult< uint8_t > parse_u8(
    const boost::json::value & value
)

function parse_u8

JsonResult< uint8_t > parse_u8(
    const boost::json::value & value,
    std::string_view field_name
)

function parse_u32

JsonResult< uint32_t > parse_u32(
    const boost::json::value & value
)

function parse_u32

JsonResult< uint32_t > parse_u32(
    const boost::json::value & value,
    std::string_view field_name
)

function parse_u64

JsonResult< uint64_t > parse_u64(
    const boost::json::value & value
)

function parse_u64

JsonResult< uint64_t > parse_u64(
    const boost::json::value & value,
    std::string_view field_name
)

function parse_schema_object

JsonResult< JsonParsedObject > parse_schema_object(
    std::string_view json_text,
    const JsonSchemaObject & schema
)

function parse_schema_object

JsonResult< JsonParsedObject > parse_schema_object(
    const boost::json::object & object,
    const JsonSchemaObject & schema
)

function parse_schema_value

JsonResult< JsonParsedValue > parse_schema_value(
    const boost::json::value & value,
    JsonFieldType type,
    const JsonSchemaObject * object_schema,
    const JsonSchemaArray * array_schema,
    std::string_view field_path
)

function get_parsed_value

JsonResult< const JsonParsedValue * > get_parsed_value(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_string

JsonResult< std::string > get_parsed_string(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_bool

JsonResult< bool > get_parsed_bool(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_u8

JsonResult< uint8_t > get_parsed_u8(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_u32

JsonResult< uint32_t > get_parsed_u32(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_u64

JsonResult< uint64_t > get_parsed_u64(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_size

JsonResult< size_t > get_parsed_size(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_array

JsonResult< const JsonParsedArray * > get_parsed_array(
    const JsonParsedObject & object,
    std::string_view field_name
)

function get_parsed_object

JsonResult< const JsonParsedObject * > get_parsed_object(
    const JsonParsedObject & object,
    std::string_view field_name
)

Source code

// Copyright 2026 Genius Ventures, Inc.
// SPDX-License-Identifier: MIT

#include <base/json_utility.hpp>

#include <limits>
#include <utility>

namespace rlp::base::json
{

namespace {

template <typename T>
[[nodiscard]] JsonResult<T> parse_unsigned_integer(const boost::json::value& value) noexcept
{
    if ( !value.is_int64() || value.as_int64() < 0 )
    {
        return outcome::failure( JsonError{JsonErrorCode::kWrongType, {}} );
    }

    const auto parsed = static_cast<uint64_t>( value.as_int64() );
    if ( parsed > static_cast<uint64_t>( std::numeric_limits<T>::max() ) )
    {
        return outcome::failure( JsonError{JsonErrorCode::kOutOfRange, {}} );
    }

    return static_cast<T>( parsed );
}

[[nodiscard]] JsonError with_field(JsonError error, std::string_view field_name)
{
    error.field = std::string( field_name );
    return error;
}

[[nodiscard]] std::string join_path(std::string_view prefix, std::string_view field_name)
{
    if ( prefix.empty() )
    {
        return std::string( field_name );
    }
    std::string path( prefix );
    path.push_back( '.' );
    path.append( field_name );
    return path;
}

[[nodiscard]] std::string array_path(std::string_view prefix, size_t index)
{
    std::string path( prefix );
    path.push_back( '[' );
    path.append( std::to_string( index ) );
    path.push_back( ']' );
    return path;
}

[[nodiscard]] JsonResult<JsonParsedObject> parse_schema_object_at_path(
    const boost::json::object& object,
    const JsonSchemaObject&    schema,
    std::string_view           path_prefix);

[[nodiscard]] JsonResult<JsonParsedArray> parse_schema_array_at_path(
    const boost::json::array& array,
    const JsonSchemaArray&    schema,
    std::string_view          path_prefix);

[[nodiscard]] JsonResult<JsonParsedValue> parse_schema_value_at_path(
    const boost::json::value& value,
    JsonFieldType             type,
    const JsonSchemaObject*   object_schema,
    const JsonSchemaArray*    array_schema,
    std::string_view          field_path)
{
    switch ( type )
    {
        case JsonFieldType::kString:
        {
            const auto parsed = parse_string( value, field_path );
            if ( !parsed )
            {
                return outcome::failure( parsed.error() );
            }
            return JsonParsedValue{type, parsed.value()};
        }
        case JsonFieldType::kBool:
        {
            const auto parsed = parse_bool( value, field_path );
            if ( !parsed )
            {
                return outcome::failure( parsed.error() );
            }
            return JsonParsedValue{type, parsed.value()};
        }
        case JsonFieldType::kU8:
        {
            const auto parsed = parse_u8( value, field_path );
            if ( !parsed )
            {
                return outcome::failure( parsed.error() );
            }
            return JsonParsedValue{type, parsed.value()};
        }
        case JsonFieldType::kU32:
        {
            const auto parsed = parse_u32( value, field_path );
            if ( !parsed )
            {
                return outcome::failure( parsed.error() );
            }
            return JsonParsedValue{type, parsed.value()};
        }
        case JsonFieldType::kU64:
        {
            const auto parsed = parse_u64( value, field_path );
            if ( !parsed )
            {
                return outcome::failure( parsed.error() );
            }
            return JsonParsedValue{type, parsed.value()};
        }
        case JsonFieldType::kSize:
        {
            const auto parsed = parse_size_t( value );
            if ( !parsed )
            {
                return outcome::failure( with_field( parsed.error(), field_path ) );
            }
            return JsonParsedValue{type, parsed.value()};
        }
        case JsonFieldType::kObject:
        {
            if ( object_schema == nullptr || !value.is_object() )
            {
                return outcome::failure( JsonError{JsonErrorCode::kWrongType, std::string( field_path )} );
            }
            const auto parsed = parse_schema_object_at_path( value.as_object(), *object_schema, field_path );
            if ( !parsed )
            {
                return outcome::failure( parsed.error() );
            }
            return JsonParsedValue{type, std::make_shared<JsonParsedObject>( parsed.value() )};
        }
        case JsonFieldType::kArray:
        {
            if ( array_schema == nullptr || !value.is_array() )
            {
                return outcome::failure( JsonError{JsonErrorCode::kWrongType, std::string( field_path )} );
            }
            const auto parsed = parse_schema_array_at_path( value.as_array(), *array_schema, field_path );
            if ( !parsed )
            {
                return outcome::failure( parsed.error() );
            }
            return JsonParsedValue{type, std::make_shared<JsonParsedArray>( parsed.value() )};
        }
    }
    return outcome::failure( JsonError{JsonErrorCode::kWrongType, std::string( field_path )} );
}

[[nodiscard]] JsonResult<JsonParsedArray> parse_schema_array_at_path(
    const boost::json::array& array,
    const JsonSchemaArray&    schema,
    std::string_view          path_prefix)
{
    JsonParsedArray parsed;
    parsed.values.reserve( array.size() );

    for ( size_t index = 0; index < array.size(); ++index )
    {
        const auto item_path = array_path( path_prefix, index );
        auto item = parse_schema_value_at_path(
            array[index],
            schema.element_type,
            schema.object_schema,
            schema.array_schema,
            item_path );
        if ( !item )
        {
            return outcome::failure( item.error() );
        }
        parsed.values.push_back( std::move( item.value() ) );
    }

    return parsed;
}

[[nodiscard]] JsonResult<JsonParsedObject> parse_schema_object_at_path(
    const boost::json::object& object,
    const JsonSchemaObject&    schema,
    std::string_view           path_prefix)
{
    JsonParsedObject parsed;
    parsed.fields.reserve( schema.fields.size() );

    for ( const auto& field : schema.fields )
    {
        const auto field_path = join_path( path_prefix, field.name );
        const auto* value = object.if_contains( std::string( field.name ) );
        if ( value == nullptr )
        {
            if ( field.default_value.has_value() )
            {
                auto default_value = parse_schema_value_at_path(
                    *field.default_value,
                    field.type,
                    field.object_schema,
                    field.array_schema,
                    field_path );
                if ( !default_value )
                {
                    return outcome::failure( default_value.error() );
                }
                parsed.fields.emplace( std::string( field.name ), std::move( default_value.value() ) );
                continue;
            }

            if ( field.required )
            {
                return outcome::failure( JsonError{JsonErrorCode::kMissingField, field_path} );
            }
            continue;
        }

        auto parsed_value = parse_schema_value_at_path(
            *value,
            field.type,
            field.object_schema,
            field.array_schema,
            field_path );
        if ( !parsed_value )
        {
            return outcome::failure( parsed_value.error() );
        }
        parsed.fields.emplace( std::string( field.name ), std::move( parsed_value.value() ) );
    }

    return parsed;
}

template <typename T>
[[nodiscard]] JsonResult<T> get_parsed_as(
    const JsonParsedObject& object,
    std::string_view        field_name,
    JsonFieldType           expected_type)
{
    const auto value = get_parsed_value( object, field_name );
    if ( !value )
    {
        return outcome::failure( value.error() );
    }
    if ( value.value()->type != expected_type )
    {
        return outcome::failure( JsonError{JsonErrorCode::kWrongType, std::string( field_name )} );
    }
    const auto* parsed = std::get_if<T>( &value.value()->value );
    if ( parsed == nullptr )
    {
        return outcome::failure( JsonError{JsonErrorCode::kWrongType, std::string( field_name )} );
    }
    return *parsed;
}

} // namespace

const char* to_string(JsonErrorCode code) noexcept
{
    switch ( code )
    {
        case JsonErrorCode::kParseFailed: return "parse failed";
        case JsonErrorCode::kRootNotObject: return "root is not an object";
        case JsonErrorCode::kMissingField: return "missing field";
        case JsonErrorCode::kWrongType: return "wrong type";
        case JsonErrorCode::kOutOfRange: return "out of range";
        case JsonErrorCode::kFileOpenFailed: return "file open failed";
    }
    return "unknown JSON error";
}

JsonResult<boost::json::object> parse_object(std::string_view json_text)
{
    boost::system::error_code ec;
    const auto parsed = boost::json::parse( json_text, ec );
    if ( ec )
    {
        return outcome::failure( JsonError{JsonErrorCode::kParseFailed, {}} );
    }

    const auto* object = parsed.if_object();
    if ( object == nullptr )
    {
        return outcome::failure( JsonError{JsonErrorCode::kRootNotObject, {}} );
    }

    return *object;
}

JsonResult<const boost::json::value*> get_value(
    const boost::json::object& object,
    std::string_view           field_name)
{
    const auto* value = object.if_contains( std::string( field_name ) );
    if ( value == nullptr )
    {
        return outcome::failure( JsonError{JsonErrorCode::kMissingField, std::string( field_name )} );
    }
    return value;
}

JsonResult<std::string> get_string(
    const boost::json::object& object,
    std::string_view           field_name)
{
    const auto value = get_value( object, field_name );
    if ( !value )
    {
        return outcome::failure( value.error() );
    }
    return parse_string( *value.value(), field_name );
}

JsonResult<std::string> parse_string(const boost::json::value& value) noexcept
{
    if ( !value.is_string() )
    {
        return outcome::failure( JsonError{JsonErrorCode::kWrongType, {}} );
    }

    const auto& string_value = value.as_string();
    return std::string( string_value.data(), string_value.size() );
}

JsonResult<std::string> parse_string(
    const boost::json::value& value,
    std::string_view          field_name) noexcept
{
    const auto parsed = parse_string( value );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

JsonResult<std::string> get_optional_string(
    const boost::json::object& object,
    std::string_view           field_name,
    std::string                default_value)
{
    const auto* value = object.if_contains( std::string( field_name ) );
    if ( value == nullptr )
    {
        return default_value;
    }
    if ( !value->is_string() )
    {
        return outcome::failure( JsonError{JsonErrorCode::kWrongType, std::string( field_name )} );
    }

    const auto& string_value = value->as_string();
    return std::string( string_value.data(), string_value.size() );
}

JsonResult<bool> get_bool(
    const boost::json::object& object,
    std::string_view           field_name)
{
    const auto value = get_value( object, field_name );
    if ( !value )
    {
        return outcome::failure( value.error() );
    }
    return parse_bool( *value.value(), field_name );
}

JsonResult<bool> parse_bool(const boost::json::value& value) noexcept
{
    if ( !value.is_bool() )
    {
        return outcome::failure( JsonError{JsonErrorCode::kWrongType, {}} );
    }
    return value.as_bool();
}

JsonResult<bool> parse_bool(
    const boost::json::value& value,
    std::string_view          field_name) noexcept
{
    const auto parsed = parse_bool( value );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

JsonResult<const boost::json::array*> get_array(
    const boost::json::object& object,
    std::string_view           field_name)
{
    const auto value = get_value( object, field_name );
    if ( !value )
    {
        return outcome::failure( value.error() );
    }
    const auto* array = value.value()->if_array();
    if ( array == nullptr )
    {
        return outcome::failure( JsonError{JsonErrorCode::kWrongType, std::string( field_name )} );
    }
    return array;
}

JsonResult<uint8_t> get_u8(
    const boost::json::object& object,
    std::string_view           field_name)
{
    const auto value = get_value( object, field_name );
    if ( !value )
    {
        return outcome::failure( value.error() );
    }
    auto parsed = parse_u8( *value.value() );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

JsonResult<uint32_t> get_u32(
    const boost::json::object& object,
    std::string_view           field_name)
{
    const auto value = get_value( object, field_name );
    if ( !value )
    {
        return outcome::failure( value.error() );
    }
    auto parsed = parse_u32( *value.value() );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

JsonResult<uint64_t> get_u64(
    const boost::json::object& object,
    std::string_view           field_name)
{
    const auto value = get_value( object, field_name );
    if ( !value )
    {
        return outcome::failure( value.error() );
    }
    auto parsed = parse_u64( *value.value() );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

JsonResult<size_t> parse_size_t(const boost::json::value& value) noexcept
{
    return parse_unsigned_integer<size_t>( value );
}

JsonResult<uint8_t> parse_u8(const boost::json::value& value) noexcept
{
    return parse_unsigned_integer<uint8_t>( value );
}

JsonResult<uint8_t> parse_u8(
    const boost::json::value& value,
    std::string_view          field_name) noexcept
{
    const auto parsed = parse_u8( value );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

JsonResult<uint32_t> parse_u32(const boost::json::value& value) noexcept
{
    return parse_unsigned_integer<uint32_t>( value );
}

JsonResult<uint32_t> parse_u32(
    const boost::json::value& value,
    std::string_view          field_name) noexcept
{
    const auto parsed = parse_u32( value );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

JsonResult<uint64_t> parse_u64(const boost::json::value& value) noexcept
{
    return parse_unsigned_integer<uint64_t>( value );
}

JsonResult<uint64_t> parse_u64(
    const boost::json::value& value,
    std::string_view          field_name) noexcept
{
    const auto parsed = parse_u64( value );
    if ( !parsed )
    {
        return outcome::failure( with_field( parsed.error(), field_name ) );
    }
    return parsed.value();
}

const JsonParsedValue* JsonParsedObject::find(std::string_view field_name) const
{
    const auto found = fields.find( std::string( field_name ) );
    if ( found == fields.end() )
    {
        return nullptr;
    }
    return &found->second;
}

JsonResult<JsonParsedObject> parse_schema_object(
    std::string_view        json_text,
    const JsonSchemaObject& schema)
{
    const auto root = parse_object( json_text );
    if ( !root )
    {
        return outcome::failure( root.error() );
    }
    return parse_schema_object_at_path( root.value(), schema, {} );
}

JsonResult<JsonParsedObject> parse_schema_object(
    const boost::json::object& object,
    const JsonSchemaObject&    schema)
{
    return parse_schema_object_at_path( object, schema, {} );
}

JsonResult<JsonParsedValue> parse_schema_value(
    const boost::json::value& value,
    JsonFieldType             type,
    const JsonSchemaObject*   object_schema,
    const JsonSchemaArray*    array_schema,
    std::string_view          field_path)
{
    return parse_schema_value_at_path( value, type, object_schema, array_schema, field_path );
}

JsonResult<const JsonParsedValue*> get_parsed_value(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    const auto* value = object.find( field_name );
    if ( value == nullptr )
    {
        return outcome::failure( JsonError{JsonErrorCode::kMissingField, std::string( field_name )} );
    }
    return value;
}

JsonResult<std::string> get_parsed_string(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    return get_parsed_as<std::string>( object, field_name, JsonFieldType::kString );
}

JsonResult<bool> get_parsed_bool(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    return get_parsed_as<bool>( object, field_name, JsonFieldType::kBool );
}

JsonResult<uint8_t> get_parsed_u8(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    return get_parsed_as<uint8_t>( object, field_name, JsonFieldType::kU8 );
}

JsonResult<uint32_t> get_parsed_u32(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    return get_parsed_as<uint32_t>( object, field_name, JsonFieldType::kU32 );
}

JsonResult<uint64_t> get_parsed_u64(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    return get_parsed_as<uint64_t>( object, field_name, JsonFieldType::kU64 );
}

JsonResult<size_t> get_parsed_size(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    return get_parsed_as<size_t>( object, field_name, JsonFieldType::kSize );
}

JsonResult<const JsonParsedArray*> get_parsed_array(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    const auto array = get_parsed_as<JsonParsedArrayPtr>( object, field_name, JsonFieldType::kArray );
    if ( !array )
    {
        return outcome::failure( array.error() );
    }
    return array.value().get();
}

JsonResult<const JsonParsedObject*> get_parsed_object(
    const JsonParsedObject& object,
    std::string_view        field_name)
{
    const auto parsed_object = get_parsed_as<JsonParsedObjectPtr>( object, field_name, JsonFieldType::kObject );
    if ( !parsed_object )
    {
        return outcome::failure( parsed_object.error() );
    }
    return parsed_object.value().get();
}

} // namespace rlp::base::json

Updated on 2026-06-05 at 17:22:19 -0700