How do I deal with commas when writing Objects to CSV in Swift?
The CSV specification suggests to wrap all fields containing special characters in double quotes.
Dealing with commas in a CSV file
As others have said, you need to escape values that include quotes. Here’s a little CSV reader in C♯ that supports quoted values, including embedded quotes and carriage returns.
By the way, this is unit-tested code. I’m posting it now because this question seems to come up a lot and others may not want an entire library when simple CSV support will do.
You can use it as follows:
using System;
public class test
{
public static void Main()
{
using ( CsvReader reader = new CsvReader( "data.csv" ) )
{
foreach( string[] values in reader.RowEnumerator )
{
Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length );
}
}
Console.ReadLine();
}
}
Here are the classes. Note that you can use the Csv.Escape
function to write valid CSV as well.
using System.IO;
using System.Text.RegularExpressions;
public sealed class CsvReader : System.IDisposable
{
public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) )
{
}
public CsvReader( Stream stream )
{
__reader = new StreamReader( stream );
}
public System.Collections.IEnumerable RowEnumerator
{
get {
if ( null == __reader )
throw new System.ApplicationException( "I can't start reading without CSV input." );
__rowno = 0;
string sLine;
string sNextLine;
while ( null != ( sLine = __reader.ReadLine() ) )
{
while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) )
sLine += "\n" + sNextLine;
__rowno++;
string[] values = rexCsvSplitter.Split( sLine );
for ( int i = 0; i < values.Length; i++ )
values[i] = Csv.Unescape( values[i] );
yield return values;
}
__reader.Close();
}
}
public long RowIndex { get { return __rowno; } }
public void Dispose()
{
if ( null != __reader ) __reader.Dispose();
}
//============================================
private long __rowno = 0;
private TextReader __reader;
private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" );
private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" );
}
public static class Csv
{
public static string Escape( string s )
{
if ( s.Contains( QUOTE ) )
s = s.Replace( QUOTE, ESCAPED_QUOTE );
if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
s = QUOTE + s + QUOTE;
return s;
}
public static string Unescape( string s )
{
if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
{
s = s.Substring( 1, s.Length - 2 );
if ( s.Contains( ESCAPED_QUOTE ) )
s = s.Replace( ESCAPED_QUOTE, QUOTE );
}
return s;
}
private const string QUOTE = "\"";
private const string ESCAPED_QUOTE = "\"\"";
private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}
How to escape comma and double quote at same time for CSV file?
There are several libraries. Here are two examples:
❐ Apache Commons Lang
Apache Commons Lang includes a special class to escape or unescape strings (CSV, EcmaScript, HTML, Java, Json, XML): org.apache.commons.lang3.StringEscapeUtils
.
Escape to CSV
String escaped = StringEscapeUtils
.escapeCsv("I said \"Hey, I am 5'10\".\""); // I said "Hey, I am 5'10"."
System.out.println(escaped); // "I said ""Hey, I am 5'10""."""Unescape from CSV
String unescaped = StringEscapeUtils
.unescapeCsv("\"I said \"\"Hey, I am 5'10\"\".\"\"\""); // "I said ""Hey, I am 5'10""."""
System.out.println(unescaped); // I said "Hey, I am 5'10"."
* You can download it from here.
❐ OpenCSV
If you use OpenCSV, you will not need to worry about escape or unescape, only for write or read the content.
Writing file:
FileOutputStream fos = new FileOutputStream("awesomefile.csv");
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
CSVWriter writer = new CSVWriter(osw);
...
String[] row = {
"123",
"John",
"Smith",
"39",
"I said \"Hey, I am 5'10\".\""
};
writer.writeNext(row);
...
writer.close();
osw.close();
os.close();Reading file:
FileInputStream fis = new FileInputStream("awesomefile.csv");
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
CSVReader reader = new CSVReader(isr);
for (String[] row; (row = reader.readNext()) != null;) {
System.out.println(Arrays.toString(row));
}
reader.close();
isr.close();
fis.close();
* You can download it from here.
Creating and writing multiple lines of text to a text file (CSV)?
Reason for crash is in line for i in 0 ... user.scores.count
it should be either for i in 0 ..< user.scores.count
or for score in user.scores
.
And you are trying to write each text to a file.
That means only last write(to:..
will have effect. Everything written will be overwritten.
To fix that, create a single string contains all info then write it to the file.
For eg the write function should be like :
func writeTextFile(_ user: UserAccount) {
//Creating text file to read and write user's data (username, password, and score values)
let fileName = "UserDataQuizApp"
let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
do {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: "UserDataQuizApp") {
try fileManager.removeItem(atPath: fileName)
}
} catch {print("error when deleting \(fileName)") }
// If the directory was found, we write a file to it and read it back
if let fileURL = dir?.appendingPathComponent(fileName).appendingPathExtension("txt") {
print("The path is: \(fileURL)")
do {
var contents = [user.username, user.password]
contents.append(contentsOf: user.scores.map { "\($0)"})
let string = contents.joined(separator: ",")
print("contents \(string)")
try string.write(to: fileURL, atomically: false, encoding: .utf8)
} catch {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
}
}
How to create a CSV file using Swift
Tutorial copied from https://iostutorialjunction.com/2018/01/create-csv-file-in-swift-programmatically.html:
Step 1:
Create an array, named as "employeeArray" which will store all our records for the employees as key value objects. Also we will add dummy data to the newly created array
class ViewController: UIViewController {
var employeeArray:[Dictionary<String, AnyObject>] = Array()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for i in 1...10 {
var dct = Dictionary<String, AnyObject>()
dct.updateValue(i as AnyObject, forKey: "EmpID")
dct.updateValue("NameForEmplyee id = \(i)" as AnyObject, forKey: "EmpName")
employeeArray.append(dct)
}
}
}
Step 2: Now we have data with us, and its time to create CSV(comma separated values) file using swift programmatically. For this we will loop through our records in "employeeArray" and append them in a string. Then we will write this string to our document directory of the app. All the stuff goes in different function named as "createCSV", below is the code for the same
func createCSV(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Employee ID"),\("Employee Name")\n\n"
for dct in recArray {
csvString = csvString.appending("\(String(describing: dct["EmpID"]!)) ,\(String(describing: dct["EmpName"]!))\n")
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("CSVRec.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
Step 3: Finally we will call our function from "viewDidLoad". Below is the complete code
class ViewController: UIViewController {
var employeeArray:[Dictionary<String, AnyObject>] = Array()
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...10 {
var dct = Dictionary<String, AnyObject>()
dct.updateValue(i as AnyObject, forKey: "EmpID")
dct.updateValue("NameForEmplyee id = \(i)" as AnyObject, forKey: "EmpName")
employeeArray.append(dct)
}
createCSV(from: employeeArray)
}
func createCSV(from recArray:[Dictionary<String, AnyObject>]) {
var csvString = "\("Employee ID"),\("Employee Name")\n\n"
for dct in recArray {
csvString = csvString.appending("\(String(describing: dct["EmpID"]!)) ,\(String(describing: dct["EmpName"]!))\n")
}
let fileManager = FileManager.default
do {
let path = try fileManager.url(for: .documentDirectory, in: .allDomainsMask, appropriateFor: nil, create: false)
let fileURL = path.appendingPathComponent("CSVRec.csv")
try csvString.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("error creating file")
}
}
}
swift 3.1 how to get array or dictionary from CSV
What you want to do is splitting up the string in rows and then into columns (basically a two dimensional array of Strings). Swift already provides the components
method for that on String
structs.
func csv(data: String) -> [[String]] {
var result: [[String]] = []
let rows = data.components(separatedBy: "\n")
for row in rows {
let columns = row.components(separatedBy: ";")
result.append(columns)
}
return result
}
Then you can access any value via:
var data = readDataFromCSV(fileName: kCSVFileName, fileType: kCSVFileExtension)
data = cleanRows(file: data)
let csvRows = csv(data: data)
print(csvRows[1][1]) //UXM n. 166/167.
Related Topics
Switch Control to Toggle Ishidden
Difficulties Converting to Swift 3
Swift Enum with Custom Initializer Loses Rawvalue Initializer
How to Sleep for Few Milliseconds in Swift 2.2
How to Change Uitextfield Keyboard Type to Email in Swift
Check If the Username Exist in Firebase
Nsattributedstring and Emojis: Issue with Positions and Lengths
How to Procedurally Draw Rectangle/Lines in Swift Using Cgcontext
How to Get String from Ascii Code in Swift
How to Convert Unix Timestamp into Swift Nsdate Object
Xcode Source Kit Will Not Stop Crashing with Swift
Date/Time Natural Language Approximation in Swift
Error: Couldn't Irgen Expression, No Additional Error
How to Call Swiftui Navigationlink Conditionally
Swift Nsunknownkeyexception - Setvalue:Forundefinedkey
How to Decrease a Value Using Fieldvalue in Firestore (Swift)