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.
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);
}
}