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:
- string
- bool
- DateTime
- DateTimeOffset
- TimeSpan
- Guid
- char
- Any type implementing both ISpanParsable<TSelf> and ISpanFormattable, or IUtf8SpanFormattable and/r IUtf8SpanParsable<TSelf> when converting to/from byte.
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:
- When reading char, the comparer must implement
IAlternateEqualityComparer<ReadOnlySpan<char>, string>
. - When reading Byte/UTF8, the comparer must be either StringComparer.Ordinal or StringComparer.OrdinalIgnoreCase.