Ssp
C++ CSV parser
Install / Use
/learn @red0124/SspREADME
__________ ____
/ ___/ ___// __ \
\__ \\__ \/ /_/ /
___/ /__/ / ____/
/____/____/_/
A header only CSV parser which is fast and versatile with modern C++ API. Requires compiler with C++17 support. Can also be used to efficiently convert strings to specific types.
Conversion for floating point values invoked using fast-float .
Function traits taken from qt-creator .
Example
Lets say we have a CSV file containing students in a given format (Id,Age,Grade) and we want to parse and print all the valid values:
$ cat students.csv
James Bailey,65,2.5
Brian S. Wolfe,40,1.9
Bill (Heath) Gates,65,3.3
#include <iostream>
#include <ss/parser.hpp>
int main() {
ss::parser<ss::throw_on_error> p{"students.csv"};
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
std::cout << id << ' ' << age << ' ' << grade << std::endl;
}
return 0;
}
And if we compile and execute the program we get the following output:
$ ./a.out
James Bailey 65 2.5
Brian S. Wolfe 40 1.9
Bill (Heath) Gates 65 3.3
Features
- Works on any type
- Easy to use
- Can work without exceptions
- Works with headers
- Works with quotes, escapes and spacings
- Works with CSV data stored in buffers
- Works with values containing new lines
- Columns and rows can be ignored
- Works with any type of delimiter
- Can return whole objects composed of converted values
- Error handling can be configured
- Restrictions can be added for each column
- Works with
std::optionalandstd::variant - Works with
CRLFandLF - Conversions can be chained if invalid
- Fast
Single header
The library can be used with a single header file ssp.hpp, but it suffers a significant performance loss when converting floating point values since the fast_float library is not present within the file.
Installation
$ git clone https://github.com/red0124/ssp
$ cd ssp
$ cmake --configure .
$ sudo make install
Note, this will also install the fast_float library.
The library supports CMake and meson build systems
Usage
Headers
The parser can be told to use only certain columns by parsing the header. This can be done with the use_fields method. It accepts any number of string-like arguments or even an std::vector<std::string> with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
$ cat students_with_header.csv
Id,Age,Grade
James Bailey,65,2.5
Brian S. Wolfe,40,1.9
Bill (Heath) Gates,65,3.3
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
p.use_fields("Id", "Grade");
for(const auto& [id, grade] : p.iterate<std::string, float>()) {
std::cout << id << ' ' << grade << std::endl;
}
// ...
$ ./a.out
James Bailey 2.5
Brian S. Wolfe 1.9
Bill (Heath) Gates 3.3
The header can be ignored using the ss::ignore_header setup option or by calling the ignore_next method after the parser has been constructed. If the header has been ignored calling any method related to header usage will result in a compilation error.
ss::parser<ss::ignore_header> p{file_name};
The fields with which the parser works with can be modified at any given time. The parser can also check if a field is present within the header by using the field_exists method.
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
p.use_fields("Grade");
const auto& grade = p.get_next<std::string>();
std::cout << grade << std::endl;
if (p.field_exists("Id")) {
p.use_fields("Grade", "Id");
for (const auto& [grade, id] : p.iterate<float, std::string>()) {
std::cout << grade << ' ' << id << std::endl;
}
}
// ...
$ ./a.out
2.5
1.9 Brian S. Wolfe
3.3 Bill (Heath) Gates
The header is parsed with the same rules as other rows, the only difference is that multiline will be disabled when parsing the header. To get the data that is
present in the header as a std::vector<std::string>, the header method can be used, and to get the header line before it has been parsed, the raw_header method can be used:
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
std::cout << p.raw_header() << std::endl;
for (const auto& field: p.header()) {
std::cout << "> " << field << std::endl;
}
// ...
$ ./a.out
Id,Age,Grade
> Id
> Age
> Grade
Methods related to headers can also fail, the error handling of these is done in the same way as for other methods.
Conversions
An alternate loop to the example above would look like:
// ...
ss::parser p{"students.csv"};
while (!p.eof()) {
const auto& [id, age, grade] = p.get_next<std::string, int, float>();
if (p.valid()) {
std::cout << id << ' ' << age << ' ' << grade << std::endl;
}
}
// ...
The alternate example with exceptions disabled will be used to show some of the features of the library. The get_next method returns a tuple of objects specified inside the template type list.
If a conversion could not be applied, the method would return a tuple of default constructed objects, and the valid method would return false, for example if the third (grade) column in our CSV could not be converted to a float the conversion would fail.
If get_next is called with a tuple as template parameter it would behave identically to passing the same tuple parameters to get_next:
using student = std::tuple<std::string, int, float>;
// returns std::tuple<std::string, int, float>
auto [id, age, grade] = p.get_next<student>();
Note, it does not always return the specified tuple since the returned tuples parameters may be altered as explained below (no void, no restrictions, ...)
Whole objects can be returned using the get_object function which takes the tuple, created in a similar way as get_next does it, and creates an object out of it:
struct student {
std::string id;
int age;
float grade;
};
// returns student
auto student = p.get_object<student, std::string, int, float>();
This works with any object if the constructor could be invoked using the template arguments given to get_object:
// returns std::vector<std::string> containing 2 elements
auto vec = p.get_object<std::vector<std::string>, std::string, std::string>();
An iterator loop as in the first example which returns objects would look like:
for (const student& s : p.iterate_object<student, std::string, int, float>()) {
// ...
}
And finally, using something I personally like to do, a struct (class) with a tied method which returns a tuple of references to to the members of the struct.
struct student {
std::string id;
int age;
float grade;
auto tied() { return std::tie(id, age, grade); }
};
The method can be used to compare the object, serialize it, deserialize it, etc. Now get_next can accept such a struct and deduce the types to which to convert the CSV.
// returns student
auto s = p.get_next<student>();
This works with the iteration loop too. Note, the order in which the members of the tied method are returned must match the order of the elements in the CSV.
Buffer mode
The parser also works with buffers containing CSV data instead of files. To parse buffer data with the parser simply create the parser by giving it the buffer, as const char*, and its size. The initial example using a buffer instead of a file would look similar to this:
std::string buffer = "James Bailey,65,2.5\nBrian S. Wolfe,40,1.9\n";
ss::parser<ss::throw_on_error> p{buffer.c_str(), buffer.size()};
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
std::cout <<
Related Skills
node-connect
331.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
81.5kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
331.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
81.5kCommit, push, and open a PR
