Parsing Compact Named-Key Parameters

A short option field becomes easier to read when each value has a name. Consider subject=revontulet,lang=fi,brief: it names a subject and language, then adds a bare brief option. An application could use it to request a short fact card about the northern lights in Finnish.

The named-key parser reads this kind of field and returns its entries in order. This page shows how to give those entries meaning in your application.

Named-Key Entry Syntax and Use Cases

A named-key block is a short list of entries inside a larger piece of text. An entry may pair a key with a value, as lang=fi does, or contain only a key, as brief does. By default, commas separate entries and = separates a key from its value.

You encounter this pattern in StringFormat specifications and in configuration placeholders. It fits places where a few readable options must live inside another string, without the structure of a complete document format.

The parser can tell you which registered key it found and whether that entry carries a value. It cannot know what those choices mean to your application. That distinction matters when you decide which checks belong in the format and which belong in your own code.

Parsing Named-Key Entries and Accessing Results

First, give each option a stable identifier. An enum makes the identifiers easier to recognize in the code that handles parsed entries. Then register the spellings readers may write in Format.

Two spellings may share one identifier. For example, language and lang both mean the same option, so the application does not need separate handling for them.

To read the field, give named_key::Parser a StringCharReader and the format. readAllEntries() returns the entries in their original order. Each Entry reports its kind, value, and registered key identifier.

The following example translates the returned entries into application options. Notice that it checks the entry kind as well as the identifier: subject and language need values, while brief is a bare switch.

text/NamedKeyParsing/ParseFactRequest.cpp

void parseFactRequest() {
    enum class FactOption { Subject, Language, Brief };
    const auto format = el::named_key::Format{}
                            .setKeys({
                                {"subject"_el, static_cast<int>(FactOption::Subject)},
                                {"language"_el, static_cast<int>(FactOption::Language)},
                                {"lang"_el, static_cast<int>(FactOption::Language)},
                                {"brief"_el, static_cast<int>(FactOption::Brief)},
                            })
                            .setValueListAllowed(false);
    auto reader = el::StringCharReader{"subject=revontulet,lang=fi,brief"_el};
    auto parser = el::named_key::Parser{reader, format};

    // Translate parsed entries into the application's options.
    auto subject = el::String{};
    auto language = el::String{};
    auto brief = false;
    auto valid = true;
    for (const auto &entry : parser.readAllEntries()) {
        switch (static_cast<FactOption>(entry.keyIndex())) {
        case FactOption::Subject:
            valid = valid && entry.isKeyWithValue();
            subject = entry.value();
            break;
        case FactOption::Language:
            valid = valid && entry.isKeyWithValue();
            language = entry.value();
            break;
        case FactOption::Brief:
            valid = valid && entry.isKey();
            brief = entry.isKey();
            break;
        }
    }
    if (!valid || subject.isEmpty() || language.isEmpty()) {
        el::io::printLine("Invalid fact request"_el);
        return;
    }
    el::io::printLine("Subject: "_el, subject);
    el::io::printLine("Language: "_el, language);
    el::io::printLine("Brief: "_el, brief);
}

Output

Subject: revontulet
Language: fi
Brief: true

The sample uses the lang alias, yet the switch handles it as Language. It also accepts brief without a value. This is why the code tests both the key identifier and the entry kind before using a value.

The call to setValueListAllowed(false) makes this field a named-entry list. Without that setting, an unrecognized bare word at the beginning could be treated as a positional value instead of an unknown key.

Named-Key Parameter Validation

The parser checks the syntax and the registered keys. Your application checks rules that depend on what each key means, such as requiring a value for subject but a bare switch for brief. Keeping those checks separate lets one format serve fields whose keys have different roles.

Input can also be malformed before the application examines an entry. For input you do not control, handle ParseError; it includes a position for syntax errors, unknown keys, and duplicate identifiers. The spellings language and lang share an identifier, so language=fi,lang=sv counts as a duplicate under the default unique-key policy.

Shared Key Definitions and Allowed-Key Restrictions

If several fields use related options, define their identifiers and spellings together. Reusing the format then keeps lang tied to the same meaning everywhere. Keep that format alive while a parser uses it, because the parser holds a reference to it.

Sometimes one field accepts only part of a shared vocabulary. Before reading, call setAllowedKeys() on its parser with the permitted semantic identifiers. An alias follows its identifier, so restricting the field to Language permits both language and lang.

When fields need different syntax or value policies, give them separate formats. The Configuring Named-Key Parameter Lists and Customizing Named-Key Syntax topics explain those choices; the Named-Key Parsing reference lists the complete interface.

Complete demo sources

text/NamedKeyParsing/ParseFactRequest.cpp

// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0

#include <DemoCommon.hpp>
#include <erbsland/named_key/Format.hpp>
#include <erbsland/named_key/Parser.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <erbsland/StringCharReader.hpp>

namespace demo {

/// Parse a compact request for a fact card using semantic key identifiers.
///
/// `Format` maps each accepted spelling to an enum value. `Parser` reads entries from a `StringCharReader`; an
/// alias can share an identifier with its full name. The application interprets the returned entry kinds and values.
void parseFactRequest() {
    enum class FactOption { Subject, Language, Brief };
    const auto format = el::named_key::Format{}
                            .setKeys({
                                {"subject"_el, static_cast<int>(FactOption::Subject)},
                                {"language"_el, static_cast<int>(FactOption::Language)},
                                {"lang"_el, static_cast<int>(FactOption::Language)},
                                {"brief"_el, static_cast<int>(FactOption::Brief)},
                            })
                            .setValueListAllowed(false);
    auto reader = el::StringCharReader{"subject=revontulet,lang=fi,brief"_el};
    auto parser = el::named_key::Parser{reader, format};

    // Translate parsed entries into the application's options.
    auto subject = el::String{};
    auto language = el::String{};
    auto brief = false;
    auto valid = true;
    for (const auto &entry : parser.readAllEntries()) {
        switch (static_cast<FactOption>(entry.keyIndex())) {
        case FactOption::Subject:
            valid = valid && entry.isKeyWithValue();
            subject = entry.value();
            break;
        case FactOption::Language:
            valid = valid && entry.isKeyWithValue();
            language = entry.value();
            break;
        case FactOption::Brief:
            valid = valid && entry.isKey();
            brief = entry.isKey();
            break;
        }
    }
    if (!valid || subject.isEmpty() || language.isEmpty()) {
        el::io::printLine("Invalid fact request"_el);
        return;
    }
    el::io::printLine("Subject: "_el, subject);
    el::io::printLine("Language: "_el, language);
    el::io::printLine("Brief: "_el, brief);
}

}