Skip to main content

Csvcsharp by Yusuke Nakada

NuGet / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: Csvcsharp

Fast CSV Serializer for .NET and Unity.

Author: Yusuke Nakada

NuGet: https://www.nuget.org/packages/Csvcsharp/

You can find more details at https://github.com/nuskey8/Csv-CSharp

Source: https://github.com/nuskey8/Csv-CSharp

Author

note

Yusuke Nakada Alt text

Original Readme

note

Csv-CSharp

NuGet Releases GitHub license

English | 日本語

img

Csv-CSharp is a highly performant CSV (TSV) parser for .NET and Unity. It is designed to parse UTF-8 binaries directly and leverage Source Generators to enable serialization/deserialization between CSV (TSV) and object arrays with zero (or very low) allocation.

Installation

NuGet packages

Csv-CSharp requires .NET Standard 2.1 or higher. The package can be obtained from NuGet.

.NET CLI

dotnet add package CsvCSharp

Package Manager

Install-Package CsvCSharp

Unity

You can install Csv-CSharp in Unity by using NugetForUnity. For details, refer to the NugetForUnity README.

Quick Start

Csv-CSharp serializes/deserializes CSV data to and from arrays of classes/structs.

Define a class/struct and add the [CsvObject] attribute and the partial keyword.

[CsvObject]
public partial class Person
{
[Column(0)]
public string Name \{ get; set; }

[Column(1)]
public int Age \{ get; set; }
}

All public fields/properties of a type marked with [CsvObject] must have either the [Column] or [IgnoreMember] attribute. (An analyzer will output a compile error if it does not find either attribute on public members.)

The [Column] attribute can specify a column index as an int or a header name as a string.

To serialize this type to CSV or deserialize it from CSV, use CsvSerializer.

var array = new Person[]
{
new() \{ Name = "Alice", Age = 18 },
new() \{ Name = "Bob", Age = 23 },
new() \{ Name = "Carol", Age = 31 },
}

// Person[] -> CSV (UTF-8)
byte[] csv = CsvSerializer.Serialize(array);

// Person[] -> CSV (UTF-16)
string csvText = CsvSerializer.SerializeToString(array);

// CSV (UTF-8) -> Person[]
array = CsvSerializer.Deserialize<Person>(csv);

// CSV (UTF-16) -> Person[]
array = CsvSerializer.Deserialize<Person>(csvText);

Serialize has an overload that returns a UTF-8 encoded byte[], and you can also pass a Stream or IBufferWriter<byte> for writing. Deserialize accepts UTF-8 byte arrays as byte[] and also supports string, Stream, and ReadOnlySequence<byte>.

The default supported types for fields are sbyte, byte, short, ushort, int, uint, long, ulong, char, string, Enum, Nullable<T>, DateTime, TimeSpan, and Guid. To support other types, refer to the Extensions section.

Serialization

The class/struct passed to CsvSerializer should have the [CsvObject] attribute and the partial keyword.

By default, fields and properties with the [Column] attribute are the targets for serialization/deserialization. The [Column] attribute is mandatory for public members, but you can target private members by adding the [Column] attribute.

[CsvObject]
public partial class Person
{
[Column(0)]
public string Name \{ get; set; }

[Column(1)]
int age;

[IgnoreMember]
public int Age => age;
}

To specify header names instead of indices, use a string key.

[CsvObject]
public partial class Person
{
[Column("name")]
public string Name \{ get; set; }

[Column("age")]
public int Age \{ get; set; }
}

To use member names as keys, specify [CsvObject(keyAsPropertyName: true)]. In this case, the [Column] attribute is not required.

[CsvObject(keyAsPropertyName: true)]
public partial class Person
{
public string Name \{ get; set; }
public int Age \{ get; set; }
}

CsvDocument

If you need to directly parse CSV fields, you can use CsvDocument.

var array = new Person[]
{
new() \{ Name = "Alice", Age = 18 },
new() \{ Name = "Bob", Age = 23 },
new() \{ Name = "Carol", Age = 31 },
};

byte[] csv = CsvSerializer.Serialize(array);

// CSV (UTF-8) -> CsvDocument
var document = CsvSerializer.ConvertToDocument(csv);

foreach (var row in document.Rows)
{
var name = row["Name"].GetValue<string>();
var age = row["Age"].GetValue<int>();
}

Options

You can change CSV settings by passing CsvOptions to Serialize/Deserialize.

CsvSerializer.Serialize(array, new CsvOptions()
{
HasHeader = true, // Include header row
AllowComments = true, // Allow comments starting with '#''
NewLine = NewLineType.LF, // Newline type
Separator = SeparatorType.Comma, // Separator character
QuoteMode = QuoteMode.Minimal, // Conditions for quoting fields (Minimal quotes only strings containing escape characters)
FormatterProvider = StandardFormatterProvider.Instance, // ICsvFormatterProvider to use
});

CSV Specifications

The default settings of Csv-CSharp generally follow the specifications outlined in RFC 4180. However, please note that for performance and practicality reasons, some specifications may be disregarded.

  • The default newline character is LF instead of CRLF.
  • Records with a mismatch in the number of fields can be read without errors being output; missing fields will be set to their default values.

Extensions

Interfaces ICsvFormatter<T> and ICsvFormatterProvider are provided to customize field serialization/deserialization.

Use ICsvFormatter<T> for type serialization/deserialization. Here is an example of implementing a formatter for a struct wrapping an int.

public struct Foo
{
public int Value;

public Foo(int value)
{
this.Value = value;
}
}

public sealed class FooFormatter : ICsvFormatter<Foo>
{
public Foo Deserialize(ref CsvReader reader)
{
var value = reader.ReadInt32();
return new Foo(value);
}

public void Serialize(ref CsvWriter writer, Foo value)
{
writer.WriteInt32(value.Value);
}
}

Next, implement a formatter provider to retrieve the formatter.

public class CustomFormatterProvider : ICsvFormatterProvider
{
public static readonly ICsvFormatterProvider Instance = new CustomFormatterProvider();

CustomFormatterProvider()
{
}

static CustomFormatterProvider()
{
FormatterCache<Foo>.Formatter = new FooFormatter();
}

public ICsvFormatter<T>? GetFormatter<T>()
{
return FormatterCache<T>.Formatter;
}

static class FormatterCache<T>
{
public static readonly ICsvFormatter<T> Formatter;
}
}

You can set the created formatter provider in CsvOptions. The above CustomFormatterProvider only supports the Foo struct, so combine it with the standard formatter provider StandardFormatterProvider.

// Create a composite formatter provider combining multiple formatter providers
var provider = CompositeFormatterProvider.Create(
CustomFormatterProvider.Instance,
StandardFormatterProvider.Instance
);

CsvSerializer.Serialize(array, new CsvOptions()
{
FormatterProvider = provider
});

License

This library is released under the MIT license.

About

note

Serializer for CSV files

How to use

Example (source csproj, source files)

This is the CSharp Project that references Csvcsharp

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvCSharp" Version="1.0.0" />
</ItemGroup>

</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

 // <auto-generated />
#nullable enable
#pragma warning disable CS0162 // Unreachable code
#pragma warning disable CS0219 // Variable assigned but never used
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8601 // Possible null reference assignment
#pragma warning disable CS8602 // Possible null return
#pragma warning disable CS8604 // Possible null reference argument for parameter
#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method

using System;
using Csv;
using Csv.Annotations;
using Csv.Internal;

namespace SerializerDemo
{
partial class Person : global::Csv.ICsvSerializerRegister
{
static void RegisterCsvSerializer()
{
global::Csv.CsvSerializer.Register(GeneratedCsvSerializer.Instance);
}
class GeneratedCsvSerializer : ICsvSerializer<global::SerializerDemo.Person>
{
public static readonly GeneratedCsvSerializer Instance = new();
static readonly byte[] AgeUtf8Key = \{ 65, 103, 101 }; // Age
static readonly byte[] NameUtf8Key = \{ 78, 97, 109, 101 }; // Name

public void Serialize(ref global::Csv.CsvWriter writer, global::System.ReadOnlySpan<global::SerializerDemo.Person> values)
{
if (writer.Options.HasHeader)
{
var quoteHeader = writer.Options.QuoteMode is (global::Csv.QuoteMode.All or global::Csv.QuoteMode.NonNumeric);
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteRaw(AgeUtf8Key.AsSpan());
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteSeparator();
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteRaw(NameUtf8Key.AsSpan());
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteEndOfLine();
}
for (int i = 0; i < values.Length; i++)
{
var item = values[i];
writer.WriteInt32(item.Age);
writer.WriteSeparator();
writer.WriteString(item.Name);
if (i != values.Length - 1) writer.WriteEndOfLine();
}
}

public void Serialize(ref global::Csv.CsvWriter writer, global::System.Collections.Generic.IEnumerable<global::SerializerDemo.Person> values)
{
if (writer.Options.HasHeader)
{
var quoteHeader = writer.Options.QuoteMode is (global::Csv.QuoteMode.All or global::Csv.QuoteMode.NonNumeric);
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteRaw(AgeUtf8Key.AsSpan());
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteSeparator();
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteRaw(NameUtf8Key.AsSpan());
if (quoteHeader) writer.WriteRaw((byte)'"');
writer.WriteEndOfLine();
}
var e = values.GetEnumerator();
try
{
if (!e.MoveNext()) return;
while (true)
{
var item = e.Current;
writer.WriteInt32(item.Age);
writer.WriteSeparator();
writer.WriteString(item.Name);
if (!e.MoveNext())
{
writer.WriteEndOfLine();
break;
}
}
}
finally
{
e.Dispose();
}
}

public global::SerializerDemo.Person[] Deserialize(ref global::Csv.CsvReader reader)
{
var allowComments = reader.Options.AllowComments;
while (reader.TryReadEndOfLine(true) || (allowComments && reader.TrySkipComment(false))) \{ }
if (reader.Options.HasHeader) reader.SkipLine();
using var list = new TempList<global::SerializerDemo.Person>();
while (reader.Remaining > 0)
{
if (reader.TryReadEndOfLine()) continue;
if (allowComments && reader.TrySkipComment(false)) continue;
var __Age = default(int);
var __Name = default(string);
var ___endOfLine = false;
for (int __i = 0; __i <= 1; __i++)
{
switch (__i)
{
case 0:
__Age = reader.ReadInt32();
break;
case 1:
__Name = reader.ReadString();
break;
default:
reader.SkipField();
break;
}
if (reader.TryReadEndOfLine(true))
{
___endOfLine = true;
goto ADD_ITEM;
}
if (!reader.TryReadSeparator(false)) goto ADD_ITEM;
}

ADD_ITEM:
list.Add( new()
{
Age = __Age,
Name = __Name,
}
);

if (!___endOfLine) reader.SkipLine();
}
return list.AsSpan().ToArray();
}

public int Deserialize(ref global::Csv.CsvReader reader, global::System.Span<global::SerializerDemo.Person> destination)
{
var allowComments = reader.Options.AllowComments;
while (reader.TryReadEndOfLine(true) || (allowComments && reader.TrySkipComment(false))) \{ }
if (reader.Options.HasHeader) reader.SkipLine();
var n = 0;
while (reader.Remaining > 0)
{
if (reader.TryReadEndOfLine()) continue;
if (allowComments && reader.TrySkipComment(false)) continue;
var __Age = default(int);
var __Name = default(string);
var ___endOfLine = false;
for (int __i = 0; __i <= 1; __i++)
{
switch (__i)
{
case 0:
__Age = reader.ReadInt32();
break;
case 1:
__Name = reader.ReadString();
break;
default:
reader.SkipField();
break;
}
if (reader.TryReadEndOfLine(true))
{
___endOfLine = true;
goto ADD_ITEM;
}
if (!reader.TryReadSeparator(false)) goto ADD_ITEM;
}

ADD_ITEM:
destination[n++] = new()
{
Age = __Age,
Name = __Name,
}
;

if (!___endOfLine) reader.SkipLine();
}
return n;
}
}
}
#pragma warning restore CS0162 // Unreachable code
#pragma warning restore CS0219 // Variable assigned but never used
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8601 // Possible null reference assignment
#pragma warning restore CS8602 // Possible null return
#pragma warning restore CS8604 // Possible null reference argument for parameter
#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method
}

Useful

Download Example (.NET C#)

Share Csvcsharp

https://ignatandrei.github.io/RSCG_Examples/v2/docs/Csvcsharp

Category "Serializer" has the following generators:

1 Csvcsharp

2 GenPack

3 jsonConverterSourceGenerator

4 JsonPolymorphicGenerator

5 Nino

6 ProtobufSourceGenerator

7 Schema

8 StackXML

9 System.Text.Json

10 VYaml

See category

Serializer