Parse CSV file in swift

10,885

Solution 1

Use the CSwiftV parser instead: https://github.com/Daniel1of1/CSwiftV

It actually handles quoted text, and therefore it handles both line breaks and commas in text. SwiftCSV cost me some time in that it doesn't handle that. But I did learn about the CSV format and parsing it ;)

Solution 2

I recommend using CSVImporter – it takes care of things like quoted text (following RFC 4180) for you and even handles very large files without problems.

Compared to other solutions it works both asynchronously (prevents delays) and reads your CSV file line by line instead of loading the entire String into memory (prevents memory issues). On top of that it is easy to use and provides beautiful callbacks for indicating failure, progress, completion and even data mapping if you desire to.


You can use it like this to get an array of Strings per line:

let path = "path/to/your/CSV/file"
let importer = CSVImporter<[String]>(path: path)
importer.startImportingRecords { $0 }.onFinish { importedRecords in
    for record in importedRecords {
        // record is of type [String] and contains all data in a line
    }
}

Take advantage of more sophisticated features like header structure support like this:

// given this CSV file content
firstName,lastName
Harry,Potter
Hermione,Granger
Ron,Weasley

// you can import data in Dictionary format
let path = "path/to/Hogwarts/students"
let importer = CSVImporter<[String: String]>(path: path)
importer.startImportingRecords(structure: { (headerValues) -> Void in

    // use the header values CSVImporter has found if needed
    print(headerValues) // => ["firstName", "lastName"]

}) { $0 }.onFinish { importedRecords in

    for record in importedRecords {
        // a record is now a Dictionary with the header values as keys
        print(record) // => e.g. ["firstName": "Harry", "lastName": "Potter"]
        print(record["firstName"]) // prints "Harry" on first, "Hermione" on second run
        print(record["lastName"]) // prints "Potter" on first, "Granger" on second run
    }
}

Solution 3

Parse CSV to two-dimension array of Strings (rows and columns)

func parseCsv(_ data: String) -> [[String]] {
    // data: String = contents of a CSV file.
    // Returns: [[String]] = two-dimension array [rows][columns].
    // Data minimum two characters or fail.
    if data.count < 2 {
        return []
    }
    var a: [String] = [] // Array of columns.
    var index: String.Index = data.startIndex
    let maxIndex: String.Index = data.index(before: data.endIndex)
    var q: Bool = false // "Are we in quotes?"
    var result: [[String]] = []
    var v: String = "" // Column value.
    while index < data.endIndex {
        if q { // In quotes.
            if (data[index] == "\"") {
                // Found quote; look ahead for another.
                if index < maxIndex && data[data.index(after: index)] == "\"" {
                    // Found another quote means escaped.
                    // Increment and add to column value.
                    data.formIndex(after: &index)
                    v += String(data[index])
                } else {
                    // Next character not a quote; last quote not escaped.
                    q = !q // Toggle "Are we in quotes?"
                }
            } else {
                // Add character to column value.
                v += String(data[index])
            }
        } else { // Not in quotes.
            if data[index] == "\"" {
                // Found quote.
                q = !q // Toggle "Are we in quotes?"
            } else if data[index] == "\r" || data[index] == "\r\n" {
                // Reached end of line.
                // Column and row complete.
                a.append(v)
                v = ""
                result.append(a)
                a = []
            } else if data[index] == "," {
                // Found comma; column complete.
                a.append(v)
                v = ""
            } else {
                // Add character to column value.
                v += String(data[index])
            }
        }
        if index == maxIndex {
            // Reached end of data; flush.
            if v.count > 0 || data[data.index(before: index)] == "," {
                a.append(v)
            }
            if a.count > 0 {
                result.append(a)
            }
            break
        }
        data.formIndex(after: &index) // Increment.
    }
    return result
}

Call above with the CSV data

let dataArray: [[String]] = parseCsv(yourStringOfCsvData)

Then extract the header row

let dataHeader = dataArray.removeFirst()

I assume you want an array of dictionaries (most spreadsheet data includes mulitple rows, not just one). The next loop is for that. But if you only need a single row (and header for keys) into a single dictionary, you can study below and get the idea of how to get there.

    var da: [Dictionary<String, String>] = [] // Array of dictionaries.
    for row in dataArray {
        for (index, column) in row.enumerated() {
            var d: Dictionary<String, String> = Dictionary()
            d.updateValue(column, forKey: dataHeader[index])
            da.append(d)
        }
    }
Share:
10,885
chakshu
Author by

chakshu

Updated on July 05, 2022

Comments

  • chakshu
    chakshu over 1 year

    I am parsing data from csv file to dictionary with the help of github.
    After parsing I am getting this type of dictionary :-

    {
    "" = "";
    "\"barred_date\"" = "\"\"";
    "\"company_id\"" = "\"1\"";
    "\"company_name\"" = "\"\"";
    "\"contact_no\"" = "\"1234567890\"";
    "\"created_date\"" = "\"2015-06-01 12:43:11\"";
    "\"current_project\"" = "\"111\"";
    "\"designation\"" = "\"Developer\"";
    "\"doj\"" = "\"2015-06-01 00:00:00\"";
    "\"fin_no\"" = "\"ABC001\"";
    "\"first_name\"" = "\"sssd\"";
    "\"last_name\"" = "\"dd\"";
    "\"project_name\"" = "\"Project 1\"";
    "\"qr_code\"" = "\"12345678\"";
    "\"resignation_date\"" = "\"\"";
    "\"status\"" = "\"1\"";
    "\"work_permit_no\"" = "\"ssdda11\"";
    "\"worker_id\"" = "\"1\"";
    "\"worker_image\"" = "\"assets/uploads/workers/eb49364ca5c5d22f11db2e3c84ebfce6.jpeg\"";
    "\"worker_image_thumb\"" = "\"assets/uploads/workers/thumbs/eb49364ca5c5d22f11db2e3c84ebfce6.jpeg\"";}
    

    How can I convert this to simple dictionary. I need data like this "company_id" = "1"

    Thanks

  • Parth Barot
    Parth Barot almost 5 years
    I'm using this CSV importer and saving data through background thread. My CSV Files has 102012 Records and 1.9 MB Size though it's taking around 20 Seconds to save Records in Model. Any Solution? And I want to Store data into Core Database instead of Model Class so that I don't want to fetch CSV file every time. Any Suggestions on how to reduce fetching time and how to save in the Core database???????? @Dschee
  • Jeehut
    Jeehut almost 5 years
    Could you please provide this information either on the GitHub project as an issue with more details or in a separate SO question? That way, discussion can be more thorough and discoverable. I don't think the comments here are a great place for it.
  • Parth Barot
    Parth Barot almost 5 years
    Actually, I have implemented it with another way and now it's successfully done and it only takes around 3-4 seconds. Thanks for your reply!!! @Dschee