F# Convert CsvFile to Json object array

Viewed 147

Trying to learn F# and got stuck when trying to find a better approach of converting a csv file to a json array where each row + header is a json object in that array.

After some trial and error I finally caved and went for an ugly approach with mutable list and map. Are there any better ways this can be implemented?

let csvFileToJsonList (csvFile: FSharp.Data.CsvFile) =
    let mutable tempList = List.empty<Map<string,string>>
    let heads =
        match csvFile.Headers with
        | Some h -> h
        | None -> [|"Missing"|] // what to do here?
    let nbrOfColumns = csvFile.NumberOfColumns
    for row in csvFile.Rows do
        let columns = row.Columns
        let mutable tempMap = Map.empty<string,string>
        for i = 0 to nbrOfColumns-1  do
                tempMap <- tempMap.Add(heads.[i], columns.[i])
        tempList <- tempMap :: tempList
    System.Text.Json.JsonSerializer.Serialize(tempList)

This outputs the following which is the goal:

    [
        {
            "Header1": "Row1Val1",
            "Header2": "Row1Val2",
            "Header3": "Row1Val3",
            "Header4": "Row1Val4",
            "Header5": "Row1Val5"
        },
        {
            "Header1": "Row2Val1",
            "Header2": "Row2Val2",
            "Header3": "Row2Val3",
            "Header4": "Row2Val4",
            "Header5": "Row2Val5"
        }
    ]
2 Answers

This is about as simple as I could make it, although a longer version might be more readable for you:

let csvFileToJsonList (csvFile: FSharp.Data.CsvFile) =
    let heads = csvFile.Headers |> Option.defaultValue [||]
    csvFile.Rows
    |> Seq.map (fun row -> Seq.zip heads row.Columns |> Map)
    |> System.Text.Json.JsonSerializer.Serialize

This produces the output in the original order, which I'm assuming is preferable (your solution reverses the order).

This also assumes some headers exist, otherwise the output will be empty objects.

Description: For each row use Seq.zip to produce a sequence of header-value tuples. Pass that to the Map constructor to create a map, providing a sequence of maps, which can be serialized.

Note that using dict instead of Map might be a bit faster.

You also could use CsvProvider to create a typed object (Row)

open FSharp.Data
type Persons = 
    CsvProvider<"David,Raab,19.02.1983",
                Schema="First (string), Last (string), BirthDay(string)",
                HasHeaders=true>

let parseCsv (reader:System.IO.TextReader) = [
    let data = Persons.Load reader
    for row in data.Rows do
        Map [
            ("First",    row.First)
            ("Last",     row.Last)
            ("Birthday", row.BirthDay)
        ]
]

Returning a List of a map instead of Json, but i guess you will know how to change it to Serialze the data.

Related