5 people like it.

CSV writer

A simple CSV writer implementation as two type extensions for the Seq module. Use it with Records, Classes and Tuples. Have a look at the modified CSV reader sample from Don Symes Expert F# too http://fssnip.net/3T in order to advance this snippet using the ColumnAttribute This version adds quote enclosure support and support for seq datatype.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
module Csv

open System.IO
open Microsoft.FSharp.Reflection

type Array =
    static member join delimiter xs = 
        xs 
        |> Array.map (fun x -> x.ToString())
        |> String.concat delimiter

type Seq =
    static member write (path:string) (data:seq<'a>): 'result = 
        use writer = new StreamWriter(path)
        data
        |> Seq.iter writer.WriteLine 

    static member csv (separator:string) (headerMapping:string -> string) ( data:seq<'a>) =
        seq {
            let dataType = typeof<'a>

            let header = 
                match dataType with
                | ty when FSharpType.IsRecord ty ->
                    FSharpType.GetRecordFields dataType
                    |> Array.map (fun info -> headerMapping info.Name)                    
                | ty when FSharpType.IsTuple ty -> 
                    FSharpType.GetTupleElements dataType
                    |> Array.mapi (fun idx info -> headerMapping(string idx) )
                | _ -> dataType.GetProperties()
                       |> Array.map (fun info -> headerMapping info.Name)

            yield header |> Array.join separator
                                    
            let lines =
                match dataType with 
                | ty when FSharpType.IsRecord ty -> 
                    data |> Seq.map FSharpValue.GetRecordFields
                | ty when FSharpType.IsTuple ty ->
                    data |> Seq.map FSharpValue.GetTupleFields
                | _ -> 
                    let props = dataType.GetProperties()
                    data |> Seq.map ( fun line -> 
                              props |> Array.map ( fun prop ->
                                prop.GetValue(line, null) ))                                     

            yield! lines |> Seq.map (Array.join separator)        
        }
//Example
type Test(colA:string, colB:int) = 
    member x.ColA = colA
    member x.ColB = colB

let testData = seq { for i in 1..10 -> new Test("col"+string(i), i) }

// using all public class properties for serialization
testData
|> Seq.csv "\t" (fun propertyName -> propertyName)
|> Seq.write "test_with_class_properties.csv"

// using a tuple projection
testData
|> Seq.distinctBy (fun testInstance -> testInstance.ColA)  
|> Seq.map (fun probe -> (probe.ColB) )
|> Seq.csv "\t" (fun columnName -> 
                    match columnName with 
                    | "0" -> "ColB"
                    | _ -> columnName)
|> Seq.write "test_with_tuple_projection.csv"
module Csv
namespace System
namespace System.IO
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Reflection
Multiple items
module Array

from Microsoft.FSharp.Collections

--------------------
type Array =
  static member join : delimiter:string -> xs:'a [] -> string

Full name: Csv.Array
static member Array.join : delimiter:string -> xs:'a [] -> string

Full name: Csv.Array.join
val delimiter : string
val xs : 'a []
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val x : 'a
System.Object.ToString() : string
module String

from Microsoft.FSharp.Core
val concat : sep:string -> strings:seq<string> -> string

Full name: Microsoft.FSharp.Core.String.concat
Multiple items
module Seq

from Microsoft.FSharp.Collections

--------------------
type Seq =
  static member csv : separator:string -> headerMapping:(string -> string) -> data:seq<'a> -> seq<string>
  static member write : path:string -> data:seq<'a> -> unit

Full name: Csv.Seq
static member Seq.write : path:string -> data:seq<'a> -> unit

Full name: Csv.Seq.write
val path : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val data : seq<'a>
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
val writer : StreamWriter
Multiple items
type StreamWriter =
  inherit TextWriter
  new : stream:Stream -> StreamWriter + 6 overloads
  member AutoFlush : bool with get, set
  member BaseStream : Stream
  member Close : unit -> unit
  member Encoding : Encoding
  member Flush : unit -> unit
  member Write : value:char -> unit + 3 overloads
  static val Null : StreamWriter

Full name: System.IO.StreamWriter

--------------------
StreamWriter(stream: Stream) : unit
StreamWriter(path: string) : unit
StreamWriter(stream: Stream, encoding: System.Text.Encoding) : unit
StreamWriter(path: string, append: bool) : unit
StreamWriter(stream: Stream, encoding: System.Text.Encoding, bufferSize: int) : unit
StreamWriter(path: string, append: bool, encoding: System.Text.Encoding) : unit
StreamWriter(path: string, append: bool, encoding: System.Text.Encoding, bufferSize: int) : unit
val iter : action:('T -> unit) -> source:seq<'T> -> unit

Full name: Microsoft.FSharp.Collections.Seq.iter
TextWriter.WriteLine() : unit
   (+0 other overloads)
TextWriter.WriteLine(value: obj) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: string) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: decimal) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: float) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: float32) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: uint64) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: int64) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: uint32) : unit
   (+0 other overloads)
TextWriter.WriteLine(value: int) : unit
   (+0 other overloads)
static member Seq.csv : separator:string -> headerMapping:(string -> string) -> data:seq<'a> -> seq<string>

Full name: Csv.Seq.csv
val separator : string
val headerMapping : (string -> string)
val dataType : System.Type
val typeof<'T> : System.Type

Full name: Microsoft.FSharp.Core.Operators.typeof
val header : string []
val ty : System.Type
type FSharpType =
  static member GetExceptionFields : exceptionType:Type * ?bindingFlags:BindingFlags -> PropertyInfo []
  static member GetFunctionElements : functionType:Type -> Type * Type
  static member GetRecordFields : recordType:Type * ?bindingFlags:BindingFlags -> PropertyInfo []
  static member GetTupleElements : tupleType:Type -> Type []
  static member GetUnionCases : unionType:Type * ?bindingFlags:BindingFlags -> UnionCaseInfo []
  static member IsExceptionRepresentation : exceptionType:Type * ?bindingFlags:BindingFlags -> bool
  static member IsFunction : typ:Type -> bool
  static member IsModule : typ:Type -> bool
  static member IsRecord : typ:Type * ?bindingFlags:BindingFlags -> bool
  static member IsTuple : typ:Type -> bool
  ...

Full name: Microsoft.FSharp.Reflection.FSharpType
static member FSharpType.IsRecord : typ:System.Type * ?allowAccessToPrivateRepresentation:bool -> bool
static member FSharpType.IsRecord : typ:System.Type * ?bindingFlags:System.Reflection.BindingFlags -> bool
static member FSharpType.GetRecordFields : recordType:System.Type * ?allowAccessToPrivateRepresentation:bool -> System.Reflection.PropertyInfo []
static member FSharpType.GetRecordFields : recordType:System.Type * ?bindingFlags:System.Reflection.BindingFlags -> System.Reflection.PropertyInfo []
val info : System.Reflection.PropertyInfo
property System.Reflection.MemberInfo.Name: string
static member FSharpType.IsTuple : typ:System.Type -> bool
static member FSharpType.GetTupleElements : tupleType:System.Type -> System.Type []
val mapi : mapping:(int -> 'T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.mapi
val idx : int
val info : System.Type
System.Type.GetProperties() : System.Reflection.PropertyInfo []
System.Type.GetProperties(bindingAttr: System.Reflection.BindingFlags) : System.Reflection.PropertyInfo []
static member Array.join : delimiter:string -> xs:'a [] -> string
val lines : seq<obj []>
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
type FSharpValue =
  static member GetExceptionFields : exn:obj * ?bindingFlags:BindingFlags -> obj []
  static member GetRecordField : record:obj * info:PropertyInfo -> obj
  static member GetRecordFields : record:obj * ?bindingFlags:BindingFlags -> obj []
  static member GetTupleField : tuple:obj * index:int -> obj
  static member GetTupleFields : tuple:obj -> obj []
  static member GetUnionFields : value:obj * unionType:Type * ?bindingFlags:BindingFlags -> UnionCaseInfo * obj []
  static member MakeFunction : functionType:Type * implementation:(obj -> obj) -> obj
  static member MakeRecord : recordType:Type * values:obj [] * ?bindingFlags:BindingFlags -> obj
  static member MakeTuple : tupleElements:obj [] * tupleType:Type -> obj
  static member MakeUnion : unionCase:UnionCaseInfo * args:obj [] * ?bindingFlags:BindingFlags -> obj
  ...

Full name: Microsoft.FSharp.Reflection.FSharpValue
static member FSharpValue.GetRecordFields : record:obj * ?allowAccessToPrivateRepresentation:bool -> obj []
static member FSharpValue.GetRecordFields : record:obj * ?bindingFlags:System.Reflection.BindingFlags -> obj []
static member FSharpValue.GetTupleFields : tuple:obj -> obj []
val props : System.Reflection.PropertyInfo []
val line : 'a
val prop : System.Reflection.PropertyInfo
System.Reflection.PropertyInfo.GetValue(obj: obj, index: obj []) : obj
System.Reflection.PropertyInfo.GetValue(obj: obj, invokeAttr: System.Reflection.BindingFlags, binder: System.Reflection.Binder, index: obj [], culture: System.Globalization.CultureInfo) : obj
Multiple items
type Test =
  new : colA:string * colB:int -> Test
  member ColA : string
  member ColB : int

Full name: Csv.Test

--------------------
new : colA:string * colB:int -> Test
val colA : string
val colB : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val x : Test
member Test.ColA : string

Full name: Csv.Test.ColA
member Test.ColB : int

Full name: Csv.Test.ColB
val testData : seq<Test>

Full name: Csv.testData
val i : int
static member Seq.csv : separator:string -> headerMapping:(string -> string) -> data:seq<'a> -> seq<string>
val propertyName : string
static member Seq.write : path:string -> data:seq<'a> -> unit
val distinctBy : projection:('T -> 'Key) -> source:seq<'T> -> seq<'T> (requires equality)

Full name: Microsoft.FSharp.Collections.Seq.distinctBy
val testInstance : Test
property Test.ColA: string
val probe : Test
property Test.ColB: int
val columnName : string

More information

Link:http://fssnip.net/3U
Posted:7 years ago
Author:Rainer Schuster
Tags: csv , serialize , writer