Skip to content

Instantly share code, notes, and snippets.

@callmekohei
Created October 3, 2021 00:04
Show Gist options
  • Save callmekohei/c415569c28e393aee6bb6a44930690d6 to your computer and use it in GitHub Desktop.
Save callmekohei/c415569c28e393aee6bb6a44930690d6 to your computer and use it in GitHub Desktop.
fsharp and CsvHelper

CsvHelper with fsharp

Enjoy!! (^_^)/

CSV

Name,Price,Color
apple,350,red
banana,250,yellow
cherry,450,redblack

result

seq [{ Name = "apple"  
       Price = 350     
       Color = "red" }]
seq
  ["Name:letters is over 5.;行 3;banana,250,yellow";
   "Name:letters is over 5.;行 4;cherry,450,redblack"]

fsharp code

module ClassRecords

module MyValidate =
  open System.Text
  open FSharp.Core
  let either fOk fError = function
    | Ok x -> fOk x
    | Error x -> fError x
  let either2 f = function
    | Ok x -> f x
    | Error x -> f x
  let (|OverLen|_|) n = function
    | (s:string) when s.Length > n -> Some s
    | _ -> None
  let guardOverLen label (sb:StringBuilder) n = function
    | OverLen n s -> Error(sb.Append($"{label}:letters is over {n}.") |> ignore ; s)
    | s -> Ok s

open System.Collections.Concurrent
open System.Globalization
open System.IO
open CsvHelper
open CsvHelper.Configuration

exception MyCsvException of string

[<CLIMutableAttribute>]
type MyCsv = {Name:string;Price:int;Color:string }
type MyCsvMap () as this =
  inherit ClassMap<MyCsv>()
  let sb = System.Text.StringBuilder()
  let csv = Unchecked.defaultof<MyCsv>
  let dic = Microsoft.FSharp.Reflection.FSharpType.GetRecordFields(typeof<MyCsv>) |> Seq.mapi(fun idx x -> x.Name,idx) |> Map.ofSeq
  let fieldValue (rObj:CsvHelper.ConvertFromStringArgs) columnName = ( columnName |> dic.TryFind |> fun x -> x.Value) |> rObj.Row.GetField<'T>
  do
    // validate all fields at first column
    this.Map(fun x -> x.Name)
    |> fun x -> x.Index(0)
    |> fun x -> x.Convert(this.RowFieldsValidate)
    |> ignore
    this.Map(fun x -> x.Price).Index(1) |> ignore
    this.Map(fun x -> x.Color).Index(2) |> ignore

  member this.RowFieldsValidate:CsvHelper.ConvertFromStringArgs -> string =
    fun rowObj ->
      sb.Clear() |> ignore
      
      (fieldValue rowObj (nameof(csv.Name)))
      |> MyValidate.guardOverLen "Name" sb 5
      |> ignore

      if sb.ToString() <> ""
      then raise <| MyCsvException (sb.ToString())
      (fieldValue rowObj (nameof(csv.Name)))


let cq = new ConcurrentQueue<MyCsv>()
let cqBad = new ConcurrentQueue<string>()

let readCsv () =
  let config = CsvConfiguration(CultureInfo.CurrentCulture)
  config.NewLine <- "\n"
  config.HasHeaderRecord <- true
  let csv = new CsvReader( new StreamReader("./sample.csv") , config)
  csv.Context.RegisterClassMap<MyCsvMap>() |> ignore

  // Reading by hand
  let mutable flg = true
  while flg do
    try
      flg <- csv.Read()
      if flg
      then
        cq.Enqueue(csv.GetRecord<MyCsv>())
    with
      | :? CsvHelperException as e ->
        match e.InnerException with
        | :? MyCsvException as e ->
          cqBad.Enqueue(
            e.Data0
            + ";行 " + (csv.Context.Parser.RawRow.ToString())
            + ";" + (csv.Context.Parser.RawRecord)
          )
        | _ -> cqBad.Enqueue( e.Message )
      | e ->
        cqBad.Enqueue( e.Message )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment