nh2
nh2

Reputation: 620

How do you use CsvHelper.CsvWriter with F# option types?

I am trying to save an F# record collection to a csv file using the .Net CsvHelper library. The problem is that option types are not being converted to string correctly.

#r "nuget: CsvHelper"

open System.IO 
open System.Globalization
open CsvHelper

type Record = { X : string; Y : float option }

let writeString x =
    use writer = new StringWriter()
    use csv = new CsvWriter(writer, CultureInfo.InvariantCulture)
    csv.WriteRecords(x)
    writer.ToString()

[{ X = "hi"; Y = Some 1.00}
 { X = "bye"; Y = None }]
|> writeString

I expect a blank value (or any other nan value that CsvProvider will understand) for the second field in the second data row here. Instead, CsvHelper is converting the None value to 0.

val it : string = "X,Value
hi,1
bye,0
"

I am aware that FSharp.Data.CsvProvider would let me convert this simple record to a CsvFile.Row that can be saved. But that's a poor solution when you have many fields because it is hard to keep the order of the fields in the CsvFile.Row straight (imagine 50 float fields). I'd prefer a solution in which the record fields are converted automatically.

Upvotes: 3

Views: 485

Answers (1)

Brian Berns
Brian Berns

Reputation: 17038

It looks like there's no built-in support for F# in CsvHelper, so you probably have to write your own type converter to handle options. Here's a quick one I whipped up:

open System.IO 
open System.Globalization
open CsvHelper
open CsvHelper.TypeConversion

type OptionConverter<'T>() =
    inherit DefaultTypeConverter()
    override __.ConvertToString(value, row, memberMapData) =
        match value :?> Option<'T> with
            | None -> ""
            | Some x -> base.ConvertToString(x, row, memberMapData)

type Record = { X : string; Y : float option }

let writeString x =
    use writer = new StringWriter()
    use csv = new CsvWriter(writer, CultureInfo.InvariantCulture)
    csv.Context.TypeConverterCache.AddConverter<Option<float>>(OptionConverter<float>())
    csv.WriteRecords(x)
    writer.ToString()

[<EntryPoint>] 
let main _ =
    [{ X = "hi"; Y = Some 1.00}
     { X = "bye"; Y = None }]
        |> writeString
        |> printfn "%s"
    0

Output is:

X,Y
hi,1
bye,

FWIW, there are CSV libraries that are easier to use from F#, such as the FSharp.Data CSV provider.

Upvotes: 6

Related Questions