View#

View classes provide efficient, zero-copy access to character streams for parsing and serialization. They are the foundation of Join’s JSON and MessagePack readers, offering a unified interface across different input sources with minimal overhead.

View classes are designed to be:

  • zero-copy — direct access to underlying data without allocation
  • type-agnostic — works with strings, streams, and buffers
  • efficient — optimized for parsing hot paths
  • flexible — seekable and non-seekable variants

View types#

Join provides several view classes optimized for different input sources:

TypeUse caseSeekableZero-copy
StringViewIn-memory strings and buffers
StringStreamViewstd::stringstream and similar
FileStreamViewFile streams
StreamViewPipes, network streams
BufferingView<T>Adds buffering to any viewDepends

StringView#

The fastest view for in-memory data with zero allocations.

Construction#

const char* data = R"({"key": "value"})";

// From pointer and length
StringView view1(data, strlen(data));

// From pointer pair
StringView view2(data, data + strlen(data));

// From null-terminated string
StringView view3(data);

Basic operations#

StringView view(data, length);

// Read single character
int c = view.get();

// Peek without advancing
int next = view.peek();

// Conditional extraction
if (view.getIf('{'))
{
    // Character was '{'
}

// Case-insensitive check
if (view.getIfNoCase('t'))
{
    // Matched 't' or 'T'
}

Bulk operations#

// Read multiple characters
char buffer[64];
size_t nread = view.read(buffer, sizeof(buffer));

// Read until escaped character
std::string content;
view.readUntilEscaped(content);  // Fast string building

Position management#

// Save position
auto pos = view.tell();

// Parse something...

// Restore position
view.seek(pos);

Stream views#

For std::istream based input.

Using stream views#

std::ifstream file("data.json");
FileStreamView view(file);

std::stringstream ss(jsonData);
StringStreamView view2(ss);

// Non-seekable (pipes, network)
StreamView view3(std::cin);

Operations#

Stream views provide the same interface as StringView:

FileStreamView view(file);

view.get();
view.peek();
view.getIf('"');
view.read(buffer, count);
view.readUntilEscaped(str);

// Seekable streams only
auto pos = view.tell();
view.seek(pos);

Whitespace handling#

All views include optimized whitespace skipping.

Skip whitespace#

// Skip spaces, tabs, newlines, carriage returns
view.skipWhitespaces();

if (view.peek() == '{')
{
    // First non-whitespace character
}

Skip whitespace and comments#

Useful for relaxed JSON parsing:

// Skip whitespace, // comments, and /* block comments */
if (view.skipWhitespacesAndComments() == 0)
{
    // Success
}

Comments supported:

  • // single line
  • /* multi-line */
StringView view(R"(
    // This is a comment
    {
        /* Block comment */
        "key": "value"
    }
)");

view.skipWhitespacesAndComments();
// Now at '{'

BufferingView#

Wraps any view to capture consumed data.

Seekable buffering#

For seekable views, uses position tracking:

StringView base(data, length);
BufferingView<StringView> view(base);

view.get();
view.get();
view.get();

// Get what was consumed
std::string consumed;
view.consume(consumed);  // Contains 3 characters

Non-seekable buffering#

For non-seekable views, uses thread-local buffer:

StreamView base(std::cin);
BufferingView<StreamView> view(base);

// Parse token
while (std::isalnum(view.peek()))
{
    view.get();
}

// Extract parsed token
std::string token;
view.consume(token);

Snapshot vs consume#

BufferingView<StringView> view(base);

// Parse something
view.get();
view.get();

// Get copy without clearing buffer
std::string snapshot;
view.snapshot(snapshot);

// Get and clear buffer
std::string consumed;
view.consume(consumed);

Usage with parsers#

Views are designed for efficient parsing.

JSON parsing example#

void parseString(StringView& view, std::string& out)
{
    view.getIf('"');  // Skip opening quote

    while (true)
    {
        view.readUntilEscaped(out);

        int c = view.peek();
        if (c == '"')
        {
            view.get();
            break;
        }
        if (c == '\\')
        {
            // Handle escape sequence
            view.get();
            c = view.get();
            // ... process escape
        }
    }
}

Parser integration#

template <typename ViewType>
class JsonReader
{
    ViewType& _view;

public:
    JsonReader(ViewType& view) : _view(view)
    {}

    int parseValue(Value& val)
    {
        _view.skipWhitespaces();

        int c = _view.peek();
        switch (c)
        {
            case '{': return parseObject(val);
            case '[': return parseArray(val);
            case '"': return parseString(val);
            // ...
        }
    }
};

Performance characteristics#

StringView#

  • Zero allocations for read operations
  • Direct memory access via pointer arithmetic
  • Branch-predicted loops for scanning
  • Cache-friendly sequential access
  • Lookup tables for character classification

Stream views#

  • Minimal virtual calls via std::streambuf
  • Buffered I/O by underlying stream
  • Seekable when stream supports it

BufferingView#

  • Thread-local storage for non-seekable views
  • No copying for seekable views (position-based)
  • Amortized O(1) character buffering

Best practices#

Choose the right view#

// Best: Zero-copy for in-memory data
StringView view(data.c_str(), data.size());

// Good: For file I/O
std::ifstream file("data.json");
FileStreamView view(file);

// Necessary: For pipes/network
StreamView view(socket_stream);

Minimize allocations#

// Good: Reserve capacity
std::string result;
result.reserve(estimated_size);
view.readUntilEscaped(result);

// Avoid: Growing incrementally
std::string result;
while (...)
{
    result += view.get();  // May reallocate
}

Use buffering when needed#

// Need to extract tokens
BufferingView<StringView> view(base);

while (parseToken(view))
{
    std::string token;
    view.consume(token);
    processToken(token);
}

Handle errors#

if (view.skipWhitespacesAndComments() != 0)
{
    // Unclosed comment
    return -1;
}

int c = view.get();
if (c == std::char_traits<char>::eof())
{
    // Unexpected end of input
    return -1;
}

Advanced features#

Case-insensitive matching#

Useful for parsing protocols:

// Match "true", "True", "TRUE", etc.
if (view.getIfNoCase('t') &&
    view.getIfNoCase('r') &&
    view.getIfNoCase('u') &&
    view.getIfNoCase('e'))
{
    return true;
}

Optimized scanning#

readUntilEscaped uses lookup tables:

// Efficiently scans until: ", \, or control characters
view.readUntilEscaped(out);

// Implemented with:
// - Aligned lookup table (64-byte)
// - Single table lookup per character
// - No branches in hot path

Type traits#

Check view capabilities at compile-time:

template <typename ViewType>
void process(ViewType& view)
{
    if constexpr (is_seekable<ViewType>::value)
    {
        auto pos = view.tell();
        // ... can seek
    }
    else
    {
        BufferingView<ViewType> buffered(view);
        // ... use buffering instead
    }
}

Summary#

FeatureSupported
Zero-copy string access
Stream-based parsing
Seekable and non-seekable
Comment parsing
Buffering adapter
Case-insensitive matching
Thread-safe
Optimized hot paths