Skip to content
/ FlameCsv Public

High performance RFC 4180 compliant CSV parsing library for .NET 6+

License

Notifications You must be signed in to change notification settings

ovska/FlameCsv

Repository files navigation

FlameCsv

High-performance RFC 4180-compliant CSV library for .NET 8+ with trimming/AOT support

Features

  • Usage
    • Straightforward API with minimal fiddling unless needed
    • Supports reading both char and byte (UTF-8)
    • Read from TextReader, Stream, PipeReader, ReadOnlySequence, string, etc.
    • Write to StringBuilder, TextWriter, Stream, file, etc.
    • Converter API similar to System.Text.Json to customize parsing
    • When needed, ccess to low level types used to parse CSV
  • Data binding
    • Supports both binding to classes/structs and reading records and individual fields manually
    • Supports binding to CSV headers, or column indexes
    • Supports complex object initialization, e.g. a combination of properties and constructor params
  • Configuration
    • Supports both RFC4180/Excel and Unix/escaped CSV styles
    • Automatic newline detection between \n and \r\n when reading text or UTF-8
  • Performance
    • Built with performance in mind from the ground up
    • Minimal allocations when reading records asynchronously
    • Near-zero allocations when reading records synchronously
    • Near-zero allocations when enumerating CSV (e.g. peeking fields)
  • Source generator
    • NativeAOT / trimming compatible
    • Same feature list and API as reflection based binding

Examples

Reading records

string data = "id,name,lastlogin\n1,Bob,2010-01-01\n2,Alice,2024-05-22";

foreach (var user in CsvReader.Read<User>(data))
{
    Console.WriteLine(user);
}

record User(int Id, string Name, DateTime LastLogin, int? Age = null);

Reading headerless CSV

string data = "1,Bob,2010-01-01\n2,Alice,2024-05-22";
var options = new CsvTextOptions { HasHeader = false };

foreach (var user in CsvReader.Read<User>(data, options))
{
    Console.WriteLine(user);
}

class User
{
    [CsvIndex(0)] public int Id { get; set; }
    [CsvIndex(1)] public string? Name { get; set; }
    [CsvIndex(2)] public DateTime LastLogin { get; set; }
}

Reading UTF8 directly from bytes

var options = new CsvUtf8Options { /* configure here */ };
await foreach (var user in CsvReader.ReadAsync<User>(File.OpenRead(@"C:\test.csv"), options))
{
    Console.WriteLine(user);
}

Source gen (NativeAOT/trimming)

foreach (var user in CsvReader.Read<User>(data, UserTypeMap.Instance))
{
    Console.WriteLine(user);
}

record User(int Id, string Name, DateTime LastLogin, int? Age = null);

[CsvTypeMap<char, User>]
partial class UserTypeMap;

Reading fields manually

string data = "id,name,lastlogin,age\n1,Bob,2010-01-01,42\n2,Alice,2024-05-22,\n";

// case insensitive header names (enabled by default)
var options = new CsvTextOptions { Comparer = StringComparer.OrdinalIgnoreCase };

foreach (CsvValueRecord<char> record in CsvReader.Enumerate(data, options))
{
    // get fields by column index of header name
    var u1 = new User(
        Id:        record.GetField<int>(0),
        Name:      record.GetField<string>(1),
        LastLogin: record.GetField<DateTime>(2),
        Age:       record.GetFieldCount() >= 3 ? record.GetField<int?>(3) : null);

    var u2 = new User(
        Id:        record.GetField<int>("Id"),
        Name:      record.GetField<string>("Name"),
        LastLogin: record.GetField<DateTime>("LastLogin"),
        Age:       record.GetFieldCount() >= 3 ? record.GetField<int?>("Age") : null);
}

Copy CSV structure into objects for later use

The CsvValueRecord<T> struct wraps around buffers from the CSV data source directly. To access the records safely outside a foreach, use AsEnumerable() or manually call new CsvRecord<T>(csvValueRecord).

string data = "id,name,lastlogin,age\n1,Bob,2010-01-01,42\n2,Alice,2024-05-22,\n";

// CsvRecord reference type copies the CSV fields for later use
List<CsvRecord<char>> records = [.. CsvReader.Enumerate(data).AsEnumerable()];
Console.WriteLine("First name: " + records[0].GetField(1));

Writing records

User[] data =
[
    new User(1, "Bob", DateTime.UnixEpoch, 42),
    new User(2, "Alice", DateTime.UnixEpoch, null),
];

StringBuilder result = CsvWriter.WriteToString(data);
Console.WriteLine(result);

record User(int Id, string Name, DateTime LastLogin, int? Age = null);

Writing fields manually

var output = new MemoryStream();
await using (var writer = CsvWriter.Create(output))
{
    writer.WriteRaw("id,name,lastlogin"u8);
    writer.NextRecord();

    writer.WriteField(1);
    writer.WriteField("Bob");
    writer.WriteField(DateTime.UnixEpoch);
    writer.WriteField(42);
    writer.NextRecord();

    writer.WriteField(2);
    writer.WriteField("Alice");
    writer.WriteField(DateTime.UnixEpoch);
    writer.WriteField(ReadOnlySpan<char>.Empty, skipEscaping: true);
    writer.NextRecord();
}

Console.WriteLine(Encoding.UTF8.GetString(output.ToArray()));

record User(int Id, string Name, DateTime LastLogin, int? Age = null);

Writing records manually

var output = new StringWriter();
using var writer = CsvWriter.Create(output);

User[] data =
[
    new User(1, "Bob", DateTime.UnixEpoch, 42),
    new User(2, "Alice", DateTime.UnixEpoch, null),
];

writer.WriteHeader<User>();

foreach (User item in data)
{
    writer.WriteRecord<User>(item);
}

writer.Flush();

Console.WriteLine(output.ToString());

record User(int Id, string Name, DateTime LastLogin, int? Age = null);

Performance

Note: CsvHelper was chosen because it is the most popular CSV library for C#. This library isn't meant to be a replacement for CsvHelper. Example CSV file used in th benchmarks is found in the TestData folder in the tests-project.


image

image


image

image

--

image

image

About

High performance RFC 4180 compliant CSV parsing library for .NET 6+

Topics

Resources

License

Stars

Watchers

Forks

Languages