Table of Contents

Converters

Overview

CsvConverter<T, TValue> is used to convert types. It follows the TryParse / TryFormat -pattern.

The value passed to TryParse is the complete unescaped/unquoted CSV field, and should represent a complete value. If the value is valid, the method should return true and the parsed value in the out parameter. If it is not valid, the method must return false. This method must not throw exceptions even with invalid data, as the library provides an informative exception if parsing fails.

The TryFormat method should attempt to write the specified value to the provided buffer, and return the number of characters written. If the buffer is not large enough, the method must return false. If the value is invalid (e.g., it can never be written), the method must throw an exception instead.

You can find converter examples in the repository.

Factories

The CsvConverterFactory<T> type can be inherited to provide custom converters for types at runtime. This pattern is also used in System.Text.Json to provide support for generic types and enums.

public class ListConverterFactory : CsvConverterFactory<char>
{
    public override bool CanConvert(Type type)
    {
        // don't allow nested collections
        return !type.IsAssignableTo(typeof(IEnumerable));
    }
    
    public override CsvConverter<char> CreateConverter(Type type, CsvOptions<char> options)
    {
        return (CsvConverter<char>)Activator.CreateInstance(
            typeof(MyListConverter<>).MakeGenericType(type),
            options);
    }
}

CsvOptions<char> options = new()
{
    Converters = { new ListConverterFactory() }
};
Warning

Due to the dynamic code generation and reflection requirements of factories, they are not supported by the source generator.

Custom

Custom converters can be added to CsvOptions<T>.Converters. Converters are checked in "Last In, First Out" (LIFO) order, falling back to built-in converters if no user configured converter can convert a specific type.

CsvOptions<char> options = new()
{
    Converters = { new CustomIntConverter() }
};

// returns an instance of CustomIntConverter
CsvConverter<char, int> converter = options.GetConverter<int>();

Built-in

Configuration

The format provider can be configured on per-type basis with the CsvOptions<T>.FormatProviders dictionary. If none is configured, the CsvOptions<T>.FormatProvider property is used. This value defaults to CultureInfo.InvariantCulture.

CsvOptions<char> options = new()
{
    FormatProvider = CultureInfo.CurrentCulture,
    FormatProviders = { [typeof(double)] = CultureInfo.InvariantCulture },
};

_ = options.GetFormatProvider(typeof(object)); // current
_ = options.GetFormatProvider(typeof(double)); // invariant
_ = options.GetFormatProvider(typeof(double?)); // invariant
Note

All the type-indexed dictionaries consider value types and their nullable counterparts equal, e.g., you only need to add either int or int? to the dictionary.

Primitives

The following primitive types are supported by default:

Most of these types can be configured using CsvOptions<T>.FormatProviders and CsvOptions<T>.Formats.

Numeric types

Conversion of numeric types can be further configured with CsvOptions<T>.NumberStyles. The default is NumberStyles.Integer for integer types, and NumberStyles.Float for floating point types.

The following numeric types are supported by default:

CsvOptions<char> options = new()
{
    NumberStyles = { [typeof(decimal)] = NumberStyles.Currency }
};

// decimals are explicitly formatted as currency
options.GetNumberStyles(typeof(decimal), defaultValue: NumberStyles.Float); // returns Currency
options.GetNumberStyles(typeof(double), defaultValue: NumberStyles.Float); // returns Float

Enums

Enum format defaults to CsvOptions<T>.EnumFormat unless configured explicitly with CsvOptions<T>.Formats.

CsvOptions<T>.IgnoreEnumCase can be used to make the parsing case-insensitive.

CsvOptions<T>.AllowUndefinedEnumValues can be used to forego a IsDefined-check when parsing enums.

// more lenient enum parsing
CsvOptions<char> options = new()
{
    IgnoreEnumCase = true,
    AllowUndefinedEnumValues = true,
    EnumFormat = "G", // format as strings
};

var converter = options.GetConverter<DayOfWeek>();

converter.TryParse("SUNDAY", out _); // OK - ignore case
converter.TryParse("10", out _); // OK - undefined but valid number
converter.TryFormat(new char[64], DayOfWeek.Monday, out int charsWritten); // writes "Monday"  instead of "0"
Tip

For extremely performant NativeAOT and trimming compatible enum converters, see the source generator.

Nullable types

Nullable<T> is supported by default as long as the underlying type can be converted. When reading nullable types, CsvOptions<T>.NullTokens can be used to specify tokens that represent null values for each type. Otherwise, the converters default to CsvOptions<T>.Null, which defaults to an empty string.

When writing any value that is null (both nullable structs or reference types), the configured null token is used. Converters can signal to the writer that they have their own null handling with the CsvConverter<T, TValue>.CanFormatNull, in which case a null TValue is passed to the converter's TryFormat method.

For performance reasons, the built-in converter for string implements this behavior, and writes null as an empty string. If you need to format empty and null strings differently, you can create a custom converter.

CsvOptions<char> options = new()
{
    Null = "null",
    NullTokens =
    {
        [typeof(int)] = "_",
        [typeof(string)] = "",
    }
};

options.GetNullToken(typeof(int?)); // returns "_"
options.GetNullToken(typeof(string)); // returns ""
options.GetNullToken(typeof(object)); // returns "null"

Custom true/false values

Use CsvOptions<T>.BooleanValues to customize which field values are parsed as booleans. You must specify at least one true and one false value.

Note that configuring custom boolean values globally replaces the default parsing behavior. For more granular control, consider using @"FlameCsv.Attributes.CsvBooleanValuesAttribute`1" to configure values on per-member basis.

CsvOptions<char> options = new()
{
    BooleanValues =
    {
        ("1", true),
        ("0", false)
    }
};
Warning

The built-in custom boolean converters have the following requirements for Comparer: