Change the name of headers in CSV file using CSVHelper in C# - c#

I am using CSV Helper library to produce CSV files for the user to
to populate and upload into the system. My issue is that the WriteHeader method just writes the attributes of a class with names like "PropertyValue", which is not user friendly. Is there a method I can use to make the text produced user friendly but is still able to successfully map the class to the files data?
My code looks like the following:
public ActionResult UploadPropertyCSV(HttpPostedFileBase file)
{
List<PropertyModel> properties = new List<PropertyModel>();
RIMEDb dbContext = new RIMEDb();
bool success = false;
foreach (string requestFiles in Request.Files)
{
if (file != null && file.ContentLength > 0 && file.FileName.EndsWith(".csv"))
{
using(StreamReader str = new StreamReader(file.InputStream))
{
using(CsvHelper.CsvReader theReader = new CsvHelper.CsvReader(str))
{
while (theReader.Read())
{
RIMUtil.PropertyUploadCSVRowHelper row = new RIMUtil.PropertyUploadCSVRowHelper()
{
UnitNumber = theReader.GetField(0),
StreetNumber = theReader.GetField(1),
StreetName = theReader.GetField(2),
AlternateAddress = theReader.GetField(3),
City = theReader.GetField(4)
};
Property property = new Property();
property.UnitNumber = row.UnitNumber;
property.StreetNumber = row.StreetNumber;
property.StreetName = row.StreetName;
property.AlternateAddress = row.AlternateAddress;
property.City = dbContext.PostalCodes.Where(p => p.PostalCode1 == row.PostalCode).FirstOrDefault().City;
dbContext.Properties.Add(property);
try
{
dbContext.SaveChanges();
success = true;
}
catch(System.Data.Entity.Validation.DbEntityValidationException ex)
{
success = false;
RIMUtil.LogError("Ptoblem validating fields in database. Please check your CSV file for errors.");
}
catch(Exception e)
{
RIMUtil.LogError("Error saving property to database. Please check your CSV file for errors.");
}
}
}
}
}
}
return Json(success);
}
I'm wondering if theres some metadata tag or something I can put on top of each attribute in my PropertyUploadCSVRowHelper class to put the text I want produced in the file
Thanks in advance

Not sure if this existed 2 years ago but now, we can change the property/column name by using the following attribute function:
[CsvHelper.Configuration.Attributes.Name("Column/Field Name")]
Full code:
using CsvHelper;
using System.Collections.Generic;
using System.IO;
namespace Test
{
class Program
{
class CsvColumns
{
private string column_01;
[CsvHelper.Configuration.Attributes.Name("Column 01")] // changes header/column name Column_01 to Column 01
public string Column_01 { get => column_01; set => column_01 = value; }
}
static void Main(string[] args)
{
List<CsvColumns> csvOutput = new List<CsvColumns>();
CsvColumns rows = new CsvColumns();
rows.Column_01 = "data1";
csvOutput.Add(rows);
string filename = "test.csv";
using (StreamWriter writer = File.CreateText(filename))
{
CsvWriter csv = new CsvWriter(writer);
csv.WriteRecords(csvOutput);
}
}
}
}

This might not be answering your question directly as you said you wanted to use csvhelper, but if you're only writing small size files (this is a simple function that I use to generate csv. Note, csvhelper will be much better for larger files as this is just building a string and not streaming the data.
Just customise the columns array in the code below variable to suit your needs.
public string GetCsv(string[] columns, List<object[]> data)
{
StringBuilder CsvData = new StringBuilder();
//add column headers
string[] s = new string[columns.Length];
for (Int32 j = 0; j < columns.Length; j++)
{
s[j] = columns[j];
if (s[j].Contains("\"")) //replace " with ""
s[j].Replace("\"", "\"\"");
if (s[j].Contains("\"") || s[j].Contains(" ")) //add "'s around any string with space or "
s[j] = "\"" + s[j] + "\"";
}
CsvData.AppendLine(string.Join(",", s));
//add rows
foreach (var row in data)
{
for (int j = 0; j < columns.Length; j++)
{
s[j] = row[j] == null ? "" : row[j].ToString();
if (s[j].Contains("\"")) //replace " with ""
s[j].Replace("\"", "\"\"");
if (s[j].Contains("\"") || s[j].Contains(" ")) //add "'s around any string with space or "
s[j] = "\"" + s[j] + "\"";
}
CsvData.AppendLine(string.Join(",", s));
}
return CsvData.ToString();
}
Here is a fiddle example of how to use it: https://dotnetfiddle.net/2WHf6o
Good luck.

Related

Parsing CSV File with \" in C#

I'm using VB's TextField in C# to parse a CSV file. But I am getting an error when it gets to \"
using (TextFieldParser csvReader = new TextFieldParser(csvFilePath)) {
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
string[] colFields = csvReader.ReadFields();
foreach (string column in colFields)
{
DataColumn datacolumn = new DataColumn(column);
datacolumn.AllowDBNull = true;
csvData.Columns.Add(datacolumn);
}
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
for (int i = 0; i < fieldData.Length; i++)
{
if (fieldData[i] == "")
{
fieldData[i] = null;
}
}
csvData.Rows.Add(fieldData);
}
}
And this is the line in the csv that is causing the error:
"101","Brake System","Level should be between \"MIN\" and \"MAX\" marks."
I don't know how to deal with the \" in C# using TextFieldParser
If the csv file will fit into memory, you could read it in, replace each \" with "", and use a MemoryStream as the input to the the TextFieldParser:
string data = File.ReadAllText(#"C:\temp\csvdata.txt").Replace("\\\"", "\"\"");
//TODO: Use the correct Encoding.
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(data)))
{
using (TextFieldParser csvReader = new TextFieldParser(ms))
{
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
string[] colFields = csvReader.ReadFields();
foreach (string s in colFields)
{
Console.WriteLine(s);
}
}
}
Which, for your example data, outputs
101
Brake System
Level should be between "MIN" and "MAX" marks.
If you don't mind using a different library, Ctl.Data has a mode (parseMidQuotes: true) specifically to allow parsing broken CSV like this.
using (StreamReader sr = new StreamReader("data.csv"))
{
var reader = new CsvReader<Record>(sr, parseMidQuotes: true, readHeader: false);
while (reader.Read())
{
Record rec = reader.CurrentObject.Value;
rec.Description = rec.Description.Replace("\\\"", "\"");
// use record...
}
}
And define your Record object:
(Normally it would match the header of the file to the properties, but in your case with a headerless file you need to specify the order with the Column attribute.
class Record
{
[Column(Order = 0)]
public int Id { get; set; }
[Column(Order = 1)]
public string Category { get; set; }
[Column(Order = 2)]
public string Description { get; set; }
}
(Disclaimer: I'm the author of said library)
Here's how to do it using two different methods without using TextFieldParser. TextFieldParser is very slow and not recommended for use in an actual production application.
Here's the simpler method using just String methods, and assuming that it's delimited with , without any quotes or any other special CSV formatting.
FileInfo file = new FileInfo("myfile.csv");
using (TextReader reader = file.OpenText())
{
for(String line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
string[] fields = line.Split(new[] {','});
foreach(String f in fields)
{
//do whatever you need for each field
}
}
}
Now if you want to use CsvHelper (available on nuget) becaues you have a more complicated CSV file with things like quoted field, headers, or if the rows of your CSV can map directly to an object that you have then this library might help you.
Not Mapped Example:
FileInfo file = new FileInfo("myfile.csv");
using (TextReader reader = file.OpenText())
using (CsvReader csv = new CsvReader(reader))
{
csv.Configuration.Delimiter = ",";
csv.Configuration.HasHeaderRecord = false;
csv.Configuration.IgnoreQuotes = true; //if you don't use field quoting
csv.Configuration.TrimFields = true; //trim fields as you read them
csv.Configuration.WillThrowOnMissingField = false; //otherwise null fields aren't allowed
while(csv.Read())
{
myStringVar = csv.GetField<string>(0); //gets first field as string
myIntVar = csv.GetField<int>(1); //gets second field as int
... //etc, you get the idea
}
}
Mapped Example:
Mapping Class- Assumes you have a class named MyClass with the fields named field1, field2, field3
public sealed class MyClassMap : CsvClassMap<MyClass>
{
public MyClassMap()
{
Map(m => m.field1).Index(0);
Map(m => m.field2).Index(1);
Map(m => m.field3).Index(2);
}
}
Parsing Code
FileInfo file = new FileInfo("myfile.csv");
using (TextReader reader = file.OpenText())
using (CsvReader csv = new CsvReader(reader))
{
csv.Configuration.Delimiter = ",";
csv.Configuration.HasHeaderRecord = false;
csv.Configuration.IgnoreQuotes = true; //if you don't use field quoting
csv.Configuration.TrimFields = true; //trim fields as you read them
csv.Configuration.WillThrowOnMissingField = false; //otherwise null fields aren't allowed
csv.Configuration.RegisterClassMap<MyClassMap>(); //adds our mapping class to the reader
while(csv.Read())
{
myObject = csv.GetRecord<MyClass>();
//do whatever here
}
}
Both of these methods won't care that you have any strange characters like \ in your csv file.
Disclaimer: I have no relation to CsvHelper, but have had success with it in a few projects in the past in which it has made my life much easier

SqlDataReader change column names when exporting to csv

I have a query that gets report data via a SqlDataReader and sends the SqlDataReader to a method that exports the content out to a .CSV file; however, the column names are showing up in the .CSV file the way that they appear in the database which is not ideal.
I do not want to alter the query itself (changing the names to have spaces) because this query is called in another location where it maps to an object and spaces would not work. I would prefer not to create a duplicate query because maintenance could be problematic. I also do not want to modify the method that writes out the .CSV as this is a method that is globally used.
Can I modify the column names after I fill the data reader but before I send it to the .CSV method? If so, how?
If I can't do it this way, could I do it if it was a DataTable instead?
Here is the general flow:
public static SqlDataReader RunMasterCSV(Search search)
{
SqlDataReader reader = null;
using (Network network = new Network())
{
using (SqlCommand cmd = new SqlCommand("dbo.MasterReport"))
{
cmd.CommandType = CommandType.StoredProcedure;
//Parameters here...
network.FillSqlReader(cmd, ref reader);
<-- Ideally would like to find a solution here -->
return reader;
}
}
}
public FileInfo CSVFileWriter(SqlDataReader reader)
{
DeleteOldFolders();
FileInfo file = null;
if (reader != null)
{
using (reader)
{
var WriteDirectory = GetExcelOutputDirectory();
double folderToSaveInto = Math.Ceiling((double)DateTime.Now.Hour / Folder_Age_Limit.TotalHours);
string uploadFolder = GetExcelOutputDirectory() + "\\" + DateTime.Now.ToString("ddMMyyyy") + "_" + folderToSaveInto.ToString();
//Add directory for today if one does not exist
if (!Directory.Exists(uploadFolder))
Directory.CreateDirectory(uploadFolder);
//Generate random GUID fileName
file = new FileInfo(uploadFolder + "\\" + Guid.NewGuid().ToString() + ".csv");
if (file.Exists)
file.Delete();
using (file.Create()) { /*kill the file stream immediately*/};
StringBuilder sb = new StringBuilder();
if (reader.Read())
{
//write the column names
for (int i = 0; i < reader.FieldCount; i++)
{
AppendValue(sb, reader.GetName(i), (i == reader.FieldCount - 1));
}
//write the column names
for (int i = 0; i < reader.FieldCount; i++)
{
AppendValue(sb, reader[i] == DBNull.Value ? "" : reader[i].ToString(), (i == reader.FieldCount - 1));
}
int rowcounter = 1;
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
AppendValue(sb, reader[i] == DBNull.Value ? "" : reader[i].ToString(), (i == reader.FieldCount - 1));
}
rowcounter++;
if (rowcounter == MaxRowChunk)
{
using (var sw = file.AppendText())
{
sw.Write(sb.ToString());
sw.Close();
sw.Dispose();
}
sb = new StringBuilder();
rowcounter = 0;
}
}
if (sb.Length > 0)
{
//write the last bit
using (var sw = file.AppendText())
{
sw.Write(sb.ToString());
sw.Close();
sw.Dispose();
sb = new StringBuilder();
}
}
}
}
}
return file;
}
I would try a refactoring of your CSVFileWriter.
First you should add a delegate declaration
public delegate string onColumnRename(string);
Then create an overload of your CSVFileWriter where you pass the delegate together with the reader
public FileInfo CSVFileWriter(SqlDataReader reader, onColumnRename renamer)
{
// Move here all the code of the old CSVFileWriter
.....
}
Move the code of the previous CSVFileWriter to the new method and, from the old one call the new one
public FileInfo CSVFileWriter(SqlDataReader reader)
{
// Pass null for the delegate to the new version of CSVFileWriter....
return this.CSVFileWriter(reader, null)
}
This will keep existing clients of the old method happy. For them nothing has changed.....
Inside the new version of CSVFileWriter you change the code that prepare the column names
for (int i = 0; i < reader.FieldCount; i++)
{
string colName = (renamer != null ? renamer(reader.GetName(i))
: reader.GetName(i))
AppendValue(sb, colName, (i == reader.FieldCount - 1));
}
Now it is just a matter to create the renamer function that translates your column names
private string myColumnRenamer(string columnName)
{
if(columnName == "yourNameWithoutSpaces")
return "your Name with Spaces";
else
return text;
}
This could be optimized with a static dictionary to remove the list of ifs
At this point your could call the new CSVFileWriter passing your function
FileInfo fi = CSVFileWrite(reader, myColumnRenamer);

How to create a generic text file parser for any find of text file?

Want to create a generic text file parser in c# for any find of text file.Actually i have 4 application all 4 getting input data from txt file format but text files are not homogeneous in nature.i have tried fixedwithdelemition.
private static DataTable FixedWidthDiliminatedTxtRead()
{
string[] fields;
StringBuilder sb = new StringBuilder();
List<StringBuilder> lst = new List<StringBuilder>();
DataTable dtable = new DataTable();
ArrayList aList;
using (TextFieldParser tfp = new TextFieldParser(testOCC))
{
tfp.TextFieldType = FieldType.FixedWidth;
tfp.SetFieldWidths(new int[12] { 2,25,8,12,13,5,6,3,10,11,10,24 });
for (int col = 1; col < 13; ++col)
dtable.Columns.Add("COL" + col);
while (!tfp.EndOfData)
{
fields = tfp.ReadFields();
aList = new ArrayList();
for (int i = 0; i < fields.Length; ++i)
aList.Add(fields[i] as string);
if (dtable.Columns.Count == aList.Count)
dtable.Rows.Add(aList.ToArray());
}
}
return dtable;
}
but i feel its very rigid one and really varies application to application making it configgurable .any better way ..
tfp.SetFieldWidths(new int[12] { 2,25,8,12,13,5,6,3,10,11,10,24 });
File nature :
Its a report kind of file .
position of columns are very similar
row data of file id different .
I get this as a reference
http://www.codeproject.com/Articles/11698/A-Portable-and-Efficient-Generic-Parser-for-Flat-F
any other thoughts ?
If the only thing different is the field widths, you could just try sending the field widths in as a parameter:
private static DataTable FixedWidthDiliminatedTxtRead(int[] fieldWidthArray)
{
string[] fields;
StringBuilder sb = new StringBuilder();
List<StringBuilder> lst = new List<StringBuilder>();
DataTable dtable = new DataTable();
ArrayList aList;
using (TextFieldParser tfp = new TextFieldParser(testOCC))
{
tfp.TextFieldType = FieldType.FixedWidth;
tfp.SetFieldWidths(fieldWidthArray);
for (int col = 1; col < 13; ++col)
dtable.Columns.Add("COL" + col);
while (!tfp.EndOfData)
{
fields = tfp.ReadFields();
aList = new ArrayList();
for (int i = 0; i < fields.Length; ++i)
aList.Add(fields[i] as string);
if (dtable.Columns.Count == aList.Count)
dtable.Rows.Add(aList.ToArray());
}
}
return dtable;
}
If you will have more logic to grab the data, you might want to consider defining an interface or abstract class for a GenericTextParser and create concrete implementations for each other file.
Hey I made one of these last week.
I did not write it with the intentions of other people using it so I appologize in advance if its not documented well but I cleaned it up for you. ALSO I grabbed several segments of code from stack overflow so I am not the original author of several pieces of this.
The places you need to edit are the path and pathout and the seperators of text.
char[] delimiters = new char[]
So it searches for part of a word and then grabs the whole word. I used a c# console application for this.
Here you go:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace UniqueListofStringFinder
{
class Program
{
static void Main(string[] args)
{
string path = #"c:\Your Path\in.txt";
string pathOut = #"c:\Your Path\out.txt";
string data = "!";
Console.WriteLine("Current Path In is set to: " + path);
Console.WriteLine("Current Path Out is set to: " + pathOut);
Console.WriteLine(Environment.NewLine + Environment.NewLine + "Input String to Search For:");
Console.Read();
string input = Console.ReadLine();
// Delete the file if it exists.
if (!File.Exists(path))
{
// Create the file.
using (FileStream fs = File.Create(path))
{
Byte[] info =
new UTF8Encoding(true).GetBytes("This is some text in the file.");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
}
System.IO.StreamReader file = new System.IO.StreamReader(path);
List<string> Spec = new List<string>();
using (StreamReader sr = File.OpenText(path))
{
while (!file.EndOfStream)
{
string s = file.ReadLine();
if (s.Contains(input))
{
char[] delimiters = new char[] { '\r', '\n', '\t', ')', '(', ',', '=', '"', '\'', '<', '>', '$', ' ', '#', '[', ']' };
string[] parts = s.Split(delimiters,
StringSplitOptions.RemoveEmptyEntries);
foreach (string word in parts)
{
if (word.Contains(input))
{
if( word.IndexOf(input) == 0)
{
Spec.Add(word);
}
}
}
}
}
Spec.Sort();
// Open the stream and read it back.
//while ((s = sr.ReadLine()) != null)
//{
// Console.WriteLine(s);
//}
}
Console.WriteLine();
StringBuilder builder = new StringBuilder();
foreach (string s in Spec) // Loop through all strings
{
builder.Append(s).Append(Environment.NewLine); // Append string to StringBuilder
}
string result = builder.ToString(); // Get string from StringBuilder
Program a = new Program();
data = a.uniqueness(result);
int i = a.writeFile(data,pathOut);
}
public string uniqueness(string rawData )
{
if (rawData == "")
{
return "Empty Data Set";
}
List<string> dataVar = new List<string>();
List<string> holdData = new List<string>();
bool testBool = false;
using (StringReader reader = new StringReader(rawData))
{
string line;
while ((line = reader.ReadLine()) != null)
{
foreach (string s in holdData)
{
if (line == s)
{
testBool = true;
}
}
if (testBool == false)
{
holdData.Add(line);
}
testBool = false;
// Do something with the line
}
}
int i = 0;
string dataOut = "";
foreach (string s in holdData)
{
dataOut += s + "\r\n";
i++;
}
// Write the string to a file.
return dataOut;
}
public int writeFile(string dataOut, string pathOut)
{
try
{
System.IO.StreamWriter file = new System.IO.StreamWriter(pathOut);
file.WriteLine(dataOut);
file.Close();
}
catch (Exception ex)
{
dataOut += ex.ToString();
return 1;
}
return 0;
}
}
}
private static DataTable FixedWidthTxtRead(string filename, int[] fieldWidths)
{
string[] fields;
DataTable dtable = new DataTable();
ArrayList aList;
using (TextFieldParser tfp = new TextFieldParser(filename))
{
tfp.TextFieldType = FieldType.FixedWidth;
tfp.SetFieldWidths(fieldWidths);
for (int col = 1; col <= fieldWidths.length; ++col)
dtable.Columns.Add("COL" + col);
while (!tfp.EndOfData)
{
fields = tfp.ReadFields();
aList = new ArrayList();
for (int i = 0; i < fields.Length; ++i)
aList.Add(fields[i] as string);
if (dtable.Columns.Count == aList.Count) dtable.Rows.Add(aList.ToArray());
}
}
return dtable;
}
Here's what I did:
I built a factory for the type of processor needed (based on file type/format), which abstracted the file reader.
I then built a collection object that contained a set of triggers for each field I was interested in (also contained the property name for which this field is destined). This settings collection is loaded in via an XML configuration file, so all I need to change are the settings, and the base parsing process can react to how the settings are configured. Finally I built a reflection wrapper wherein once a field is parsed, the corresponding property on the model object is set.
As the file flowed through, the triggers for each setting evaluated each lines value. When it found what it was set to find (via pattern matching, or column length values) it fired and event that bubbled up and set a property on the model object. I can show some pseudo code if you're interested. It needs some work for efficiency's sake, but I like the concept.

Converting a csv file to json using C#

I was wondering if someone's written a utility to convert a CSV file to Json using C#. From a previous question on stackoverflow, I'm aware of this nice utility - https://github.com/cparker15/csv-to-json and at the moment I plan to refer to it but an existing C# implementation would be very helpful! Thanks!
If you can use System.Web.Extensions, something like this could work:
var csv = new List<string[]>(); // or, List<YourClass>
var lines = System.IO.File.ReadAllLines(#"C:\file.txt");
foreach (string line in lines)
csv.Add(line.Split(',')); // or, populate YourClass
string json = new
System.Web.Script.Serialization.JavaScriptSerializer().Serialize(csv);
You might have more complex parsing requirements for the csv file and you might have a class that encapsulates the data from one line, but the point is that you can serialize to JSON with one line of code once you have a Collection of lines.
Cinchoo ETL - an open source library available to do the conversion of CSV to JSON easily with few lines of code
For a sample CSV:
Id, Name, City
1, Tom, NY
2, Mark, NJ
3, Lou, FL
4, Smith, PA
5, Raj, DC
Sample code,
string csv = #"Id, Name, City
1, Tom, NY
2, Mark, NJ
3, Lou, FL
4, Smith, PA
5, Raj, DC
";
StringBuilder sb = new StringBuilder();
using (var p = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
)
{
using (var w = new ChoJSONWriter(sb))
w.Write(p);
}
Console.WriteLine(sb.ToString());
Output JSON:
[
{
"Id": "1",
"Name": "Tom",
"City": "NY"
},
{
"Id": "2",
"Name": "Mark",
"City": "NJ"
},
{
"Id": "3",
"Name": "Lou",
"City": "FL"
},
{
"Id": "4",
"Name": "Smith",
"City": "PA"
},
{
"Id": "5",
"Name": "Raj",
"City": "DC"
}
]
Sample fiddle: https://dotnetfiddle.net/pclnsT
Checkout CodeProject article for some additional help.
UPDATE:
If your CSV file has duplicate column names or no names, please use the below steps to produce the JSON file
string csv = #"Id, Name,
1, Tom, NY
2, Mark, NJ
3, Lou, FL
4, Smith, PA
5, Raj, DC
";
StringBuilder sb = new StringBuilder();
using (var p = ChoCSVReader.LoadText(csv)
.WithField("Id", position: 1)
.WithField("Name", position: 2)
.WithField("City", position: 3)
.WithFirstLineHeader(true)
)
{
using (var w = new ChoJSONWriter(sb))
w.Write(p);
}
Console.WriteLine(sb.ToString());
Sample fiddle: https://dotnetfiddle.net/pP5Du6
Disclaimer: I'm the author of this library.
I used Dictionary and returned json using newtonsoft
public string ConvertCsvFileToJsonObject(string path)
{
var csv = new List<string[]>();
var lines = File.ReadAllLines(path);
foreach (string line in lines)
csv.Add(line.Split(','));
var properties = lines[0].Split(',');
var listObjResult = new List<Dictionary<string, string>>();
for (int i = 1; i < lines.Length; i++)
{
var objResult = new Dictionary<string, string>();
for (int j = 0; j < properties.Length; j++)
objResult.Add(properties[j], csv[i][j]);
listObjResult.Add(objResult);
}
return JsonConvert.SerializeObject(listObjResult);
}
Install Nuget package NewtonSoft.Json
Add reference dll Microsoft.VisualBasic
using System.Linq;
using Newtonsoft.Json;
using Microsoft.VisualBasic.FileIO;
using System.IO;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Project
{
public static class Program
{
public static void Main(string[] args)
{
string CSVpath = #"D:\New Folder\information.csv";
string analyticsData = ReadFile(CSVpath);
}
private static string ReadFile(string filePath)
{
string payload = "";
try
{
if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath) && Path.GetExtension(filePath).Equals(".csv", StringComparison.InvariantCultureIgnoreCase))
{
string[] lines = File.ReadAllLines(filePath);
if (lines != null && lines.Length > 1)
{
var headers = GetHeaders(lines.First());
payload = GetPayload(headers, lines.Skip(1));
}
}
}
catch (Exception exp)
{
}
return payload;
}
private static IEnumerable<string> GetHeaders(string data)
{
IEnumerable<string> headers = null;
if (!string.IsNullOrWhiteSpace(data) && data.Contains(','))
{
headers = GetFields(data).Select(x => x.Replace(" ", ""));
}
return headers;
}
private static string GetPayload(IEnumerable<string> headers, IEnumerable<string> fields)
{
string jsonObject = "";
try
{
var dictionaryList = fields.Select(x => GetField(headers, x));
jsonObject = JsonConvert.SerializeObject(dictionaryList);
}
catch (Exception ex)
{
}
return jsonObject;
}
private static Dictionary<string, string> GetField(IEnumerable<string> headers, string fields)
{
Dictionary<string, string> dictionary = null;
if (!string.IsNullOrWhiteSpace(fields))
{
var columns = GetFields(fields);
if (columns != null && headers != null && columns.Count() == headers.Count())
{
dictionary = headers.Zip(columns, (x, y) => new { x, y }).ToDictionary(item => item.x, item => item.y);
}
}
return dictionary;
}
public static IEnumerable<string> GetFields(string line)
{
IEnumerable<string> fields = null;
using (TextReader reader = new StringReader(line))
{
using (TextFieldParser parser = new TextFieldParser(reader))
{
parser.TextFieldType = FieldType.Delimited; parser.SetDelimiters(","); fields = parser.ReadFields();
}
}
return fields;
}
}
}
Taking only a dependency on Newtonsoft.Json, here's a helper method given an array of CSV lines, the first one being the header.
public static IEnumerable<JObject> CsvToJson(IEnumerable<string> csvLines)
{
var csvLinesList = csvLines.ToList();
var header = csvLinesList[0].Split(',');
for (int i = 1; i < csvLinesList.Count; i++)
{
var thisLineSplit = csvLinesList[i].Split(',');
var pairedWithHeader = header.Zip(thisLineSplit, (h, v) => new KeyValuePair<string, string>(h, v));
yield return new JObject(pairedWithHeader.Select(j => new JProperty(j.Key, j.Value)));
}
}
I use ChoETL:
using ChoETL;
using System.IO;
public class FromCSVtoJSON
{
public FromCSVtoJSON()
{
}
public void convertFile(string inputFile, string outputFile)
{
using (var writer = new ChoJSONWriter(outputFile))
{
using (var reader = new ChoCSVReader(inputFile).WithFirstLineHeader())
{
writer.Write(reader);
}
}
}
}
From that same SO answer, there is a link to this post.
CsvToJson extention method
/// <summary>
/// Converts a CSV string to a Json array format.
/// </summary>
/// <remarks>First line in CSV must be a header with field name columns.</remarks>
/// <param name="value"></param>
/// <returns></returns>
public static string CsvToJson(this string value)
{
// Get lines.
if (value == null) return null;
string[] lines = value.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (lines.Length < 2) throw new InvalidDataException("Must have header line.");
// Get headers.
string[] headers = lines.First().SplitQuotedLine(new char[] { ',' }, false);
// Build JSON array.
StringBuilder sb = new StringBuilder();
sb.AppendLine("[");
for (int i = 1; i < lines.Length; i++)
{
string[] fields = lines[i].SplitQuotedLine(new char[] { ',', ' ' }, true, '"', false);
if (fields.Length != headers.Length) throw new InvalidDataException("Field count must match header count.");
var jsonElements = headers.Zip(fields, (header, field) => string.Format("{0}: {1}", header, field)).ToArray();
string jsonObject = "{" + string.Format("{0}", string.Join(",", jsonElements)) + "}";
if (i < lines.Length - 1)
jsonObject += ",";
sb.AppendLine(jsonObject);
}
sb.AppendLine("]");
return sb.ToString();
}
There appears to be an issue with where some methods called within the above extension live (see the comments of the original blog post), but it should get you most of the way there.
EDIT Here is another SO answer about splitting a CSV line. You could use one of the suggested regex solutions to create your own SplitQuotedLine method:
public static string SplitQuotedLine(this string value, char separator, bool quotes) {
// Use the "quotes" bool if you need to keep/strip the quotes or something...
var s = new StringBuilder();
var regex = new Regex("(?<=^|,)(\"(?:[^\"]|\"\")*\"|[^,]*)");
foreach (Match m in regex.Matches(value)) {
s.Append(m.Value);
}
return s.ToString();
}
I did not test the above, so forgive me if I made any errors.
Also, it would appear that Zip is a LINQ extension method, so that takes care of that problem.
Here's mine.. It can parse 9k CSV records in centuries. LOL
class CSVTOJSON
{
public string ConvertToJSON()
{
string json = string.Empty;
string csv = string.Empty;
using (StreamReader reader = new StreamReader("data.csv"))
{
csv = reader.ReadToEnd();
}
string[] lines = csv.Split(new string[] { "\n" }, System.StringSplitOptions.None);
if (lines.Length > 1)
{
// parse headers
string[] headers = lines[0].Split(',');
StringBuilder sbjson = new StringBuilder();
sbjson.Clear();
sbjson.Append("[");
// parse data
for (int i = 1; i < lines.Length; i++)
{
if (string.IsNullOrWhiteSpace(lines[i])) continue;
if (string.IsNullOrEmpty(lines[i])) continue;
sbjson.Append("{");
string[] data = lines[i].Split(',');
for (int h = 0; h < headers.Length; h++)
{
sbjson.Append(
$"\"{headers[h]}\": \"{data[h]}\"" + (h < headers.Length - 1 ? "," : null)
);
}
sbjson.Append("}" + (i < lines.Length - 1 ? "," : null));
}
sbjson.Append("]");
json = sbjson.ToString();
}
return json;
}
}
But it works.
console log:
Converting CSV to JSON
CSV has 9486 data
Total duration converting CSV to JSON: 00:00:00.0775373
Small variation to the solution by bc3tech i.e. avoiding external dependencies (on Newtonsoft.Json), and instead using System.Text.Json (dotnet core 3+)
public static IEnumerable<string> CsvToJson(string fileName, char delim = '|')
{
var lines = File.ReadLines(fileName);
var hdr = new List<string>(lines.First().Trim().Split(delim));
foreach (var l in lines.Skip(1).Where(l => (l.Trim() != String.Empty)))
{
var val = l.Trim().Split(delim);
var ds = hdr.Zip(val, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v);
yield return JsonSerializer.Serialize(ds);
}
}
I can see most people simply assume parsing CSV file is to simply split comma delimiter between each column, but the following format is still a valid CSV
"aaa","bbb","ccc"
"z, z",yyy,xxx
There is a nice class hidden within Microsoft.VisualBasic.FileIO to handle CSV file format correctly. I combine this with JSON.NET came up with the solution.
public static string? CsvToJson(string input, string delimiter)
{
using (TextFieldParser parser = new TextFieldParser(
new MemoryStream(Encoding.UTF8.GetBytes(input))))
{
parser.Delimiters = new string[] { delimiter };
string[]? headers = parser.ReadFields();
if (headers == null) return null;
string[]? row;
string comma = "";
var sb = new StringBuilder((int)(input.Length * 1.1));
sb.Append("[");
while ((row = parser.ReadFields()) != null)
{
var dict = new Dictionary<string, object>();
for (int i = 0; row != null && i < row.Length; i++)
dict[headers[i]] = row[i];
var obj = JsonConvert.SerializeObject(dict);
sb.Append(comma + obj);
comma = ",";
}
return sb.Append("]").ToString();
}
}
Usage
var str = #"Header1,""Header,,2 "",Data3
1,444.00, ""Liang, Jerry""
0,""5,550"",Jerry
";
var json = CsvToJson(str, ",");
Result
[
{
"Header1": "1",
"Header,,2": "444.00",
"Data3": "Liang, Jerry"
},
{
"Header1": "0441",
"Header,,2": "5,550",
"Data3": "Jerry"
}
]
I looked for the answer for this question finally i solved it by using Dictionary
public static void CreateJsonFromCSV()
{
string path = "C:\\Users\\xx\\xx\\xx\\xx\\lang.csv";
string textFilePath = path;
const Int32 BufferSize = 128;
using (var fileStream = File.OpenRead(textFilePath))
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize))
{
String line;
Dictionary<string, string> jsonRow = new Dictionary<string, string>();
while ((line = streamReader.ReadLine()) != null)
{
string[] parts = line.Split(',');
string key_ = parts[0];
string value = parts[1];
if (!jsonRow.Keys.Contains(key_))
{
jsonRow.Add(key_, value );
}
}
var json = new JavaScriptSerializer().Serialize(jsonRow);
string path_ = "C:\\XX\\XX\\XX\\XX\\XX.csv";
File.WriteAllText(path_, json);
}
}
Make sure you add the below in web.config before you do parse large csv files.
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="50000000"/>
</webServices>
</scripting>
</system.web.extensions>
Try this:
StreamReader sr = new StreamReader(filePath);
while ((line = sr.ReadLine()) != null)
{
//Console.WriteLine(line);
string[] csv = line.Split(',');
var dictionary = new Dictionary<string, string>();
dictionary.Add("dispatching_base_number",csv[0]);
dictionary.Add("available_vehicles", csv[1]);
dictionary.Add("vehicles_in_trips", csv[2]);
dictionary.Add("Cancellations", csv[3]);
string jsonN = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(dictionary);
Console.WriteLine("Sending message: {0}",jsonN);
}
Try this and convert CSV to JSON object:
public static List<object> CsvToJson( string body, string[] column ) {
if ( string.IsNullOrEmpty( body ) ) return null;
string[] rowSeparators = new string[] { "\r\n" };
string[] rows = body.Split( rowSeparators, StringSplitOptions.None );
body = null;
if ( rows == null || ( rows != null && rows.Length == 0 ) ) return null;
string[] cellSeparator = new string[] { "," };
List<object> data = new List<object>( );
int clen = column.Length;
rows.Select( row => {
if ( string.IsNullOrEmpty( row ) ) return row;
string[] cells = row.Trim( ).Split( cellSeparator, StringSplitOptions.None );
if ( cells == null ) return row;
if ( cells.Length < clen ) return row;
Dictionary<object, object> jrows = new Dictionary<object, object>( );
for ( int i = 0; i < clen; i++ ) {
jrows.Add( column[i], cells[i]?.Trim( ) );
}
data.Add( jrows );
return row;
} ).ToList( );
rowSeparators = null; rows = null;
cellSeparator = null;
return data;
}
var data = CsvToJson("csv_input_str", new string[]{ "column_map" })
string jsonStr = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }.Serialize( data );
First, load the csv file into datatable and serialize it to Json document. It uses OLEDB Provider that can parse the csv wisely,
Courtesy to Jim Scott, https://stackoverflow.com/a/1050278/6928056
Courtesy to K_B, https://stackoverflow.com/a/2979938/6928056
using System.Data;
using System.Data.OleDb;
using System.Globalization;
using System.IO;
using Newtonsoft.Json;
static string ConvertCsvToJson(string path, bool isFirstRowHeader)
{
string header = isFirstRowHeader ? "Yes" : "No";
string pathOnly = Path.GetDirectoryName(path);
string fileName = Path.GetFileName(path);
string sql = #"SELECT * FROM [" + fileName + "]";
using(OleDbConnection connection = new OleDbConnection(
#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathOnly +
";Extended Properties=\"Text;HDR=" + header + "\""))
using(OleDbCommand command = new OleDbCommand(sql, connection))
using(OleDbDataAdapter adapter = new OleDbDataAdapter(command))
{
var dataTable = new DataTable();
dataTable.Locale = CultureInfo.CurrentCulture;
adapter.Fill(dataTable);
return JsonConvert.SerializeObject(dataTable, Formatting.Indented);
}
}
Simple method to convert flat csv file to a collection of simple json formatted objects. Works with files with header row on the first line. Part of this method was found somewhere else on SO.
Add reference to Microsoft.VisualBasic.
using Microsoft.VisualBasic.FileIO;
public static StringBuilder ReadCsv()
{
var path = #"X:\...\input.csv";
using (TextFieldParser csvParser = new TextFieldParser(path))
{
csvParser.CommentTokens = new string[] { "#" };
//Remember to use your own separator
csvParser.SetDelimiters(new string[] { ";" });
csvParser.HasFieldsEnclosedInQuotes = false;
StringBuilder json = new StringBuilder();
string[] colNames = new string[0];
string[] fields = new string[0];
json.Append("[");
int counter = 0;
while (!csvParser.EndOfData)
{
if (counter == 0)
{
//Read properies' names
colNames = csvParser.ReadFields();
counter++;
Console.WriteLine($"{colNames.Length} columns detected!");
}
else
{
// Read current line fields, pointer moves to the next line.
// Read the properties' values
fields = csvParser.ReadFields();
json.Append("{");
for (int i = 0; i < colNames.Length; i++)
{
json.Append($"\"{colNames[i]}\":{TryParse(fields[i])}");
if (i != colNames.Length - 1)
{
json.Append(",");
}
}
json.Append("},");
Console.WriteLine($"Writing record nr.: {counter}");
counter++;
}
}
json.Length--; //Remove trailing comma
json.Append("]");
return json;
}
}
string TryParse(string s)
{
if (string.IsNullOrEmpty(s)) return "null";
//Remember to set your decimal character here!
if (s.Contains('.'))
{
double dResult;
//This works on my computer, could be different on your machine
if (double.TryParse(s, NumberStyles.AllowDecimalPoint,
CultureInfo.InvariantCulture, out dResult))
return dResult.ToString(CultureInfo.InvariantCulture);
}
else
{
int intResult;
if (int.TryParse(s, out intResult))
return intResult.ToString(CultureInfo.InvariantCulture);
}
return "\"" + s + "\"";
}
This should give you a simple list of json objects.
If you are looking for a C# only solution this might work for you. I recently face the same issue and created this method to overcome.
public static string ConvertToJsonStructure(string csvDataAsString, char delimiter = ',', bool hasHeaders = true)
{
var output = string.Empty;
if (string.IsNullOrEmpty(csvDataAsString))
return "{}";
var rows = csvDataAsString.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
var headers = hasHeaders ? rows[0].Split(delimiter) : null;
var colCount = headers?.Length;
var jsonStart = "{";
var jsonEnd = "}";
if (rows.Length > (1 + (hasHeaders ? 1 : 0)))
{
jsonStart = "[";
jsonEnd = "]";
}
output = output + jsonStart;
int startIndex = hasHeaders ? 1 : 0;
for (int i = startIndex; i < rows.Length; i++)
{
var cols = rows[i].Split(delimiter);
if (colCount == null)
colCount = cols.Length;
var tempJson = "{";
for (int j = 0; j < colCount.Value; j++)
{
if (hasHeaders)
tempJson = tempJson + $"\"{headers[j]}\":";
var isNumber = Regex.IsMatch(cols[j], #"^\d + $");
var val = isNumber ? cols[j] : $"\"{cols[j]}\"";
tempJson = tempJson + val;
if (j < colCount.Value - 1)
tempJson = tempJson + ",";
}
tempJson = tempJson + "}";
if (i < rows.Length - 1)
tempJson = tempJson + ",";
output = output + tempJson;
}
output = output + jsonEnd;
return output;
}
}
You need to pass your content as string to this method and you can do something like this to read your csv
var csvAsString = File.ReadAllText("TestCsv.csv");
var result = CsvToJson.ConvertToJsonStructure(csvAsString);

Using Key like "[]Server" into sortedDictionary, but why?

I am trying to understand and probably reuse part of DevExpress Demo code to save simple settings into ini file. I know I can use .NET System.Configuration doing what I want. Just for a smallish project, simple save it as a text file seem more flexible and light-weighted, at least that is what it seems.
Anyway, while I am playing with it, I am trying to understand why the code I am reading trying to add key as "[]Server" and "[]DBFormat" as key name. They do that for a reason I can not understand yet, I could probably use some help here.
Here is the Code I think relevant:
public class IniFile {
SortedDictionary<string, string> data = new SortedDictionary<string, string>();
...
public void Load(string path) {
using(StreamReader sr = new StreamReader(path)) {
string folder = "[]";
while(!sr.EndOfStream) {
string s = sr.ReadLine().Trim();
if(s.Length == 0 || s[0] == ';') continue;
if(s[0] == '[') {
folder = s;
continue;
}
string key, value;
int delim = s.IndexOf('=');
if(delim < 0) {
key = folder + s.Replace("[", string.Empty).Replace("]", string.Empty);
value = string.Empty;
} else {
key = folder + s.Remove(delim).TrimEnd().Replace("[", string.Empty).Replace("]", string.Empty);
value = s.Substring(delim + 1).TrimStart();
}
if(!data.ContainsKey(key)) data.Add(key, value);
else data[key] = value;
}
}
}
...
public void Save(string path) {
using(StreamWriter sw = new StreamWriter(path)) {
string folder = "[]";
foreach(string key in data.Keys) {
int delim = key.IndexOf(']');
string keyFolder = key.Remove(delim + 1);
string keyName = key.Substring(delim + 1);
if(keyFolder != folder) {
folder = keyFolder;
sw.WriteLine(folder);
}
sw.WriteLine(keyName + " = " + data[key]);
}
}
}
void AddRawValue(string key, string value) {
key = key.Trim();
value = value.Trim();
int folderBegin = key.IndexOf('[');
int folderEnd = key.IndexOf(']');
if(folderBegin != 0 || folderEnd < 0) throw new ArgumentException("key");
data.Add(key, value);
}
And here is part of the ini file itself:
DBFormat = "Mdb"
Login = "admin"
Password = ""
Server = "(localhost)"
Obvious, they go though the trouble to add [] into keyname for some reason, but end up not using it in the demo data. I am think they are using string inside [] to group settings?
I only skimmed it, but I'd imagine the intent might have been "[section]setting" so "[]setting" would represent a setting not in a section. Look how easy it is to get values, just a single string will do! (There seems to be confusion between a "section" and a "folder" -- which might be an above-par variable name for that code...)
Then again, I could be way off as I only invested about 10 seconds of time on that tripe >:)
Happy coding.

Categories