I'm using LinqToCSV to output a List to CSV. Snippet of sample code is as follows:
class Person
{
[CsvColumn(Name = "Name", FieldIndex = 1)]
public string UserName { get; set; }
[CsvColumn(Name = "Address 1", FieldIndex = 2)]
public string Address1 { get; set; }
[CsvColumn(Name = "Telephone", FieldIndex = 3)]
public string Telephone { get; set; }
}
private void OutputToCSV(string filenamePrefix, List<Person> people)
{
CsvFileDescription outputFileDescription = new CsvFileDescription
{
SeparatorChar = ','
FirstLineHasColumnNames = true,
FileCultureName = "en-GB"
};
CsvContext cc = new CsvContext();
cc.Write(
people,
#"C:\temp\people.csv",
outputFileDescription);
}
The issue I have is with the telephone number. If it is in the object as 0123456789012 then when I open the CSV in Excel it is seen as a number and the leading zero is removed. I'd like to pre format the column in the CSV as text.
According to Stop Excel from automatically converting certain text values to dates I can start the field with an equals and put the value in quotes, but is there an attribute I can set which will mean that LinqToCSV will do this for me? I don't really want to use LinqToCSV, then open the file and edit it to get it how I want.
Is your intent to create a CSV or an Excel file? If the latter, then wouldn't it be much easier if you have instead used Epplus from Nuget? ie:
void Main()
{
ExcelPackage pck = new ExcelPackage();
List<Person> people = new List<Person> {
new Person { UserName="Cetin", Address1="A1", Telephone="012345"},
new Person { UserName="Timbo", Address1="A2", Telephone="023456"},
new Person { UserName="StackO", Address1="A3", Telephone="0 345 6789 01 23"},
};
var wsEnum = pck.Workbook.Worksheets.Add("MyPeople");
//Load the collection starting from cell A1...
wsEnum.Cells["A1"].LoadFromCollection(people, true, TableStyles.Medium9);
wsEnum.Cells[wsEnum.Dimension.Address].AutoFitColumns();
//...and save
var fi = new FileInfo(#"d:\temp\People.xlsx");
if (fi.Exists)
{
fi.Delete();
}
pck.SaveAs(fi);
}
class Person
{
public string UserName { get; set; }
public string Address1 { get; set; }
public string Telephone { get; set; }
}
Try using OutputFormat to force it ToString(). You may also be able to combine it with your other fix and use OutputFormat="=C" but I have not tried that.
[CsvColumn(Name = "Telephone", FieldIndex = 3, OutputFormat = "C")]
public string Telephone { get; set; }
As you say, this is an Excel issue and you need to the data to have a leading = sign, so that 0123456789012 becomes ="0123456789012".
You can use the OutputFormat to change the format, but you would need to play around with the formatting:
[CsvColumn(FieldIndex = 2, OutputFormat = "dd MMM HH:mm:ss")]
Related
Im trying to add a new class called "Company" to a Json Array called Companies. I'm doing this using C# and Json .net Ive tried many different things. I have them all pares out and in Jobjects ready to be molded together but I can't find a way to do so. Im trying to get it to find "Companies" then insert the new company object in there.
This is what im trying to do.
public void CreateNewCompany()
{
Company company = new Company
{
CompanyName = textBox1.Text,
IPO = Convert.ToDouble(textBox2.Text),
Category = CategorycomboBox1.SelectedItem.ToString(),
Description = textBox4.Text,
StartDate = Convert.ToInt32(textBox5.Text)
};
AddProductListItemsToFinishedJSON(company);
AddNewsArticlesListItemsToFinishedJSON(company);
JObject newCompany = JObject.FromObject(company);
string existingFileContents = File.ReadAllText(path);
string newFileContents = newCompany.ToString();
var existingFileContentsToJSON = JObject.Parse(existingFileContents);
var newFileContentsToJSON = JObject.Parse(newFileContents);
Debug.WriteLine(existingFileContents);
SaveJSONFile(company);
}
public void SaveJSONFile(Company localcompany)
{
if (File.Exists(Path.Combine(#"D:\", "comp.json")))
{
File.Delete(Path.Combine(#"D:\", "comp.json"));
}
string RawJSON = JsonConvert.SerializeObject(localcompany);
string FormattedJSON = JToken.Parse(RawJSON).ToString(Formatting.Indented);
//Console.WriteLine(FormattedJSON);
File.WriteAllText(#"D:\comp.json", FormattedJSON);
}
These are the classes
public class Company
{
public string CompanyName { get; set; }
public double IPO { get; set; }
public string Category { get; set; }
public string Description { get; set; }
public int StartDate { get; set; }
public List<Product> Products = new List<Product>();
public List<NewsArticle> CompanySpecificNewsArticles = new List<NewsArticle>();
public List<string> EavesDropperList = new List<string>();
}
public class Product
{
[JsonProperty("ProductName")]
public string ProductName { get; set; }
}
public class NewsArticle
{
[JsonProperty("Type")]
public string Type { get; set; }
[JsonProperty("Content")]
public string Content { get; set; }
}
This is what the Json Looks like and I want to add it to 'Companies'
{
"Companies":[
{
"CompanyName":"",
"IPO":25.0,
"Category":"Gaming",
"Description":"A video game company",
"StartDate":"1-1-2000",
"Products":[
{
"ProductName":""
},
{
"ProductName":""
}
],
"CompanySpecificNewsArticles":[
{
"Type":"Positive",
"Content":"This company has had a very good year!"
},
{
"Type":"Negative",
"Content":"This company has had a very bad year!"
},
{
"Type":"Neutral",
"Content":"This company is doing okay, I guess"
}
],
"CompanySpecificEavesdropper":[
{
"Type":"Positive",
"Content":"This company has had a very good year!"
},
{
"Type":"Negative",
"Content":"This company has had a very bad year!"
},
{
"Type":"Neutral",
"Content":"This company is doing okay, I guess!"
}
]
}
//,
// Other companies omitted
]
}
A JSON file is just a text file, so there's no straightforward way to insert a record into the middle of the file. Instead, you will need to load the entire file into some in-memory representation, add your Company to the "Companies" array, then re-serialize the file back to disk.
To accomplish this, first create the following extension methods:
public class JsonExtensions
{
public static T LoadFromFileOrCreateDefault<T>(string path, JsonSerializerSettings settings = null) where T : new()
{
var serializer = JsonSerializer.CreateDefault(settings);
try
{
using (var file = File.OpenText(path))
{
return (T)JsonSerializer.CreateDefault(settings).Deserialize(file, typeof(T));
}
}
catch (FileNotFoundException)
{
return new T();
}
}
public static void SaveToFile<T>(T root, string path, Formatting formatting = Formatting.None, JsonSerializerSettings settings = null)
{
using (var file = File.CreateText(path))
using (var writer = new JsonTextWriter(file) { Formatting = formatting })
{
JsonSerializer.CreateDefault(settings).Serialize(writer, root);
}
}
}
Now you can add your Company to the array in CreateNewCompany() as follows:
var root = JsonExtensions.LoadFromFileOrCreateDefault<JObject>(Path);
var companiesArray = (JArray)root["Companies"] ?? (JArray)(root["Companies"] = new JArray());
companiesArray.Add(JObject.FromObject(company));
JsonExtensions.SaveToFile(root, Path, Formatting.Indented);
Demo fiddle #1 here.
Incidentally, since your entire file seems to have a fixed schema, you could simplify your code and get slightly better performance by deserializing directly to some root data model, omitting the JObject representation entirely.
First, create the following root data model:
public class CompanyList
{
public List<Company> Companies { get; } = new List<Company>();
}
Then modify CreateNewCompany() as follows:
var root = JsonExtensions.LoadFromFileOrCreateDefault<CompanyList>(Path);
root.Companies.Add(company);
JsonExtensions.SaveToFile(root, Path, Formatting.Indented);
Demo fiddle #2 here.
Notes:
By using generics in JsonExtensions we can use the same code to load from, and save to, a file, for both JObject and CompanyList.
Serializing directly from and to your file without loading to an intermediate string should improve performance as explained in Performance Tips: Optimize Memory Usage.
Company.StartDate is declared to be an int, however in your JSON it appears as a non-numeric string:
"StartDate": "1-1-2000"
You will need to adjust your data model to account for this.
There is no need to manually delete the old file as File.CreateText(String) creates or opens a file for writing UTF-8 encoded text. If the file already exists, its contents are overwritten.
Alternatively you might want to write to a temporary file and then overwrite the old file only after serialization finishes successfully.
It is better to catch the FileNotFoundException from File.OpenText() rather than checking File.Exists() manually in case the file is somehow deleted in between the two calls.
This should give you an idea of what you should be doing
public void CreateNewCompany()
{
Company company = new Company
{
CompanyName = "New Company",
IPO = Convert.ToDouble("0.2"),
Category = "Sample Category",
Description = "Sample Description",
StartDate = Convert.ToInt32("2009")
};
AddProductListItemsToFinishedJSON(company);
AddNewsArticlesListItemsToFinishedJSON(company);
SaveJSONFile(company);
}
public static void SaveJSONFile(Company localcompany)
{
if (File.Exists(path))
{
JObject arr = JObject.Parse(File.ReadAllText(path)));
(arr["Companies"] as JArray).Add(JToken.FromObject(localcompany));
string RawJSON = JsonConvert.SerializeObject(arr);
string FormattedJSON = JToken.Parse(RawJSON).ToString(Formatting.Indented);
File.WriteAllText(path, FormattedJSON);
}
else
{
JObject arr = new JObject();
arr.Add("Companies", new JArray());
(arr["Companies"] as JArray).Add(JToken.FromObject(localcompany));
string RawJSON = JsonConvert.SerializeObject(arr);
string FormattedJSON = JToken.Parse(RawJSON).ToString(Formatting.Indented);
File.WriteAllText(path, FormattedJSON);
}
}
Delete your existing json file then run this code. The first run creates the file with one object while subsequent runs adds object to it. You can refactor the code.
Json file will have the format below
{
"Companies": [
{
"Products": [],
"CompanySpecificNewsArticles": [],
"EavesDropperList": [],
"CompanyName": "New Company",
"IPO": 0.2,
"Category": "Sample Category",
"Description": "Sample Description",
"StartDate": 2009
},
{
"Products": [],
"CompanySpecificNewsArticles": [],
"EavesDropperList": [],
"CompanyName": "New Company",
"IPO": 0.2,
"Category": "Sample Category",
"Description": "Sample Description",
"StartDate": 2009
}
]
}
as per your comment on the order, you can set order on you class like below
public class Company
{
[JsonProperty(Order = 0)]
public string CompanyName { get; set; }
[JsonProperty(Order = 1)]
public double IPO { get; set; }
[JsonProperty(Order = 2)]
public string Category { get; set; }
[JsonProperty(Order = 3)]
public string Description { get; set; }
[JsonProperty(Order = 4)]
public int StartDate { get; set; }
[JsonProperty(Order = 5)]
public List<Product> Products = new List<Product>();
[JsonProperty(Order = 6)]
public List<NewsArticle> CompanySpecificNewsArticles = new List<NewsArticle>();
[JsonProperty(Order = 7)]
public List<string> EavesDropperList = new List<string>();
}
I have class with nested list properties, I am trying to write the value to CSV file, but I am getting output appended with [{ }] like shown below:
Client TDeals
ABC [{DealName:59045599,TShape:[{StartDate:"2014-01-
28T23:00:00",EndDate:"2014-01-28T23:30:00",Volume:0.00},
{StartDateTime:"2014-01-
28T23:30:00",EndDateTime:"2014-01-29T00:00:00",Volume:0.00}}]
I want my output in CSV file like shown below:
Client DealNo StartDate EndDate Volume
ABC 59045599 - - -
Class Properties
public class TRoot
{
public string Client { get; set; }
public List<TDeal> Deals { get; set; }
}
public class TDeal
{
public string DealName{get;set;}
public List<TInterval> TShape { get; set; }
}
public class TInterval
{
public string StartDate{ get; set; }
public string EndDate{ get; set; }
public string Volume {get;set;}
}
I am using ServiceStack.Text to create CSV file from object
ServiceStack.Text.CsvSerializer.SerializeToWriter<TRoot>(TRoot, writer);
Reference URL
https://github.com/ServiceStack/ServiceStack.Text
Define a new class for single csv line:
public class CsvLine
{
public string Client { get; set; }
public string DealName { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
public string Volume { get; set; }
}
Now you can transfrom your objects into collection of lines with Linq SelectMany method:
TRoot root = ...
var lines = root.Deals.SelectMany(d => d.TShape.Select(s => new CsvLine
{
Client = root.Client,
DealName = d.DealName,
StartDate = s.StartDate,
EndDate = s.EndDate,
Volume = s.Volume
})).ToArray();
Then call SerializeToWriter on that collection
I would recommend to "flatten" your output to CSV.
Create one more class that will be a mirror of what you would like to have in CSV file. Before writing to the file, convert your TRoot to that new class and write it to CSV.
Quite quick and elegant solution :)
You can try Cinchoo ETL to create the CSV file. First you will have to flatten out root object using Linq and pass them to CSV writer to create file.
Sample below show how to
private static void Test()
{
TRoot root = new TRoot() { Client = "ABC", Deals = new List<TDeal>() };
root.Deals.Add(new TDeal
{
DealName = "59045599",
TShape = new List<TInterval>()
{
new TInterval { StartDate = DateTime.Today.ToString(), EndDate = DateTime.Today.AddDays(2).ToString(), Volume = "100" },
new TInterval { StartDate = DateTime.Today.ToString(), EndDate = DateTime.Today.AddDays(2).ToString(), Volume = "200" }
}
});
using (var w = new ChoCSVWriter("nestedObjects.csv").WithFirstLineHeader())
{
w.Write(root.Deals.SelectMany(d => d.TShape.Select(s => new { ClientName = root.Client, DealNo = d.DealName, StartDate = s.StartDate, EndDate = s.EndDate, Volume = s.Volume })));
}
}
The output is:
ClientName,DealNo,StartDate,EndDate,Volume
ABC,59045599,1/17/2018 12:00:00 AM,1/19/2018 12:00:00 AM,100
ABC,59045599,1/17/2018 12:00:00 AM,1/19/2018 12:00:00 AM,200
For more information about it, visit the codeproject article at
https://www.codeproject.com/Articles/1155891/Cinchoo-ETL-CSVWriter
Disclaimer: I'm the author of this library.
Is there any library out there that can serialize objects with array properties to .csv?
Let's say I have this model:
public class Product
{
public string ProductName { get; set; }
public int InStock { get; set; }
public double Price { get; set; }
...
public string[] AvailableVariants { get; set; }
}
Would something like that be possible to do?
Edit: I need to present some data in a csv/excel format. The thing is, I'm not sure if there is a simple way of achieving what I want with CSV serialization libraries or if I should rather focus on writing an Excel native file.
An example of result I'm looking for:
Product Name In Stock Price Variants
ABC 241 200 Normal
CAB 300 300 Normal
Red
Blue
CBA 125 100 Normal
White
Awesome
Red
ACB 606 75 Normal
Small
Large
X-Large
What would be the most efficient way to do this?
I'm not aware of any libraries that will do this, here's a console example of how I'd approach writing/reading from a CSV:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace TestingProduct
{
class TestingProduct
{
public class Product
{
public string ProductName { get; set; }
public int InStock { get; set; }
public double Price { get; set; }
public string[] AvailableVariants { get; set; }
public override string ToString() => $"{ProductName},{InStock},{Price}{(AvailableVariants?.Length > 0 ? "," + string.Join(",", AvailableVariants) : "")}";
public static Product Parse(string csvRow)
{
var fields = csvRow.Split(',');
return new Product
{
ProductName = fields[0],
InStock = Convert.ToInt32(fields[1]),
Price= Convert.ToDouble(fields[2]),
AvailableVariants = fields.Skip(3).ToArray()
};
}
}
static void Main()
{
var prod1 = new Product
{
ProductName = "test1",
InStock= 2,
Price = 3,
AvailableVariants = new string[]{ "variant1", "variant2" }
};
var filepath = #"C:\temp\test.csv";
File.WriteAllText(filepath, prod1.ToString());
var parsedRow = File.ReadAllText(filepath);
var parsedProduct = Product.Parse(parsedRow);
Console.WriteLine(parsedProduct);
var noVariants = new Product
{
ProductName = "noVariants",
InStock = 10,
Price = 10
};
var prod3 = new Product
{
ProductName = "test2",
InStock= 5,
Price = 5,
AvailableVariants = new string[] { "variant3", "variant4" }
};
var filepath2 = #"C:\temp\test2.csv";
var productList = new List<Product> { parsedProduct, prod3, noVariants };
File.WriteAllText(filepath2, string.Join("\r\n", productList.Select(x => x.ToString())));
var csvRows = File.ReadAllText(filepath2);
var newProductList = new List<Product>();
foreach (var csvRow in csvRows.Split(new string[] { "\r\n" }, StringSplitOptions.None))
{
newProductList.Add(Product.Parse(csvRow));
}
newProductList.ForEach(Console.WriteLine);
Console.ReadKey();
}
}
}
This code will work with a class that has a single object array property. Do you need something that can handle an object with multiple array properties?
I have written some kind of library to write csv files, have a look:
public static class CsvSerializer
{
public static bool Serialize<T>(string path, IList<T> data, string delimiter = ";")
{
var csvBuilder = new StringBuilder();
var dataType = typeof(T);
var properties = dataType.GetProperties()
.Where(prop => prop.GetCustomAttribute(typeof(CsvSerialize)) == null);
//write header
foreach (var property in properties)
{
csvBuilder.Append(property.Name);
if (property != properties.Last())
{
csvBuilder.Append(delimiter);
}
}
csvBuilder.Append("\n");
//data
foreach (var dataElement in data)
{
foreach (var property in properties)
{
csvBuilder.Append(property.GetValue(dataElement));
if (property != properties.Last())
{
csvBuilder.Append(delimiter);
}
}
csvBuilder.Append("\n");
}
File.WriteAllText(path, csvBuilder.ToString());
return true;
}
}
public class CsvSerialize : Attribute
{
}
Lets pretend you want to serialize following class:
public class MyDataClass
{
[CsvSerialize]
public string Item1 {get; set;}
[CsvSerialize]
public string Item2 {get; set;}
}
Then just do:
public void SerializeData(IList<MyDataClass> data)
{
CsvSerializer.Serialize("C:\\test.csv", data);
}
It takes a IList of your class and writes a csv.
It cant serialize arrays but that would be easy to implement.
I keep getting the "Specified cast is not valid" error on my code in the ID column. I tried using
data.Columns.Add("ID", typeof(int));
to let it know that the ID is an int. I am getting the data from a local .xls file.
here's my code:
foreach (DataRow p in data.Rows)
{
TopPlayed top = new TopPlayed()
{
TrackID = p.Field<int>("ID"),
TrackName = p.Field<string>("Track Name"),
ArtistName = p.Field<string>("Artist Name"),
Times = p.Field<double>("NoOfPlays").ToString()
};
data.Columns.Add("ID", typeof(int));
daa.Add(top);
}
here's my class:
public class TopPlayed
{
public int TrackID { get; set; }
public string TrackName { get; set; }
public string ArtistName { get; set; }
public string Times { get; set; }
}
cheers in advance :)
Try using double instead. Excel exposes data as either strings or doubles.
TopPlayed top = new TopPlayed()
{
TrackID = Convert.ToInt32(p.Field<double>("ID")),
TrackName = p.Field<string>("Track Name"),
ArtistName = p.Field<string>("Artist Name"),
Times = p.Field<double>("NoOfPlays").ToString()
};
Another way is checking our type (with Watch window for example):
row.Table.Columns["id"].DataType
What print (in my case): {Name = "Int64" FullName = "System.Int64"}, then change your code to proper type:
TopPlayed top = new TopPlayed()
{
TrackID = p.Field<Int64>("ID"),
TrackName = p.Field<string>("Track Name"),
ArtistName = p.Field<string>("Artist Name"),
Times = p.Field<double>("NoOfPlays").ToString()
};
public class TopPlayed
{
public Int64 TrackID { get; set; }
public string TrackName { get; set; }
public string ArtistName { get; set; }
public string Times { get; set; }
}
If it's a query inside a query, the best way that worked is to submit a simple String
and then to convert it to int32 ex.:
instead of using
p.Field("ID")
do it
row["ID"]
This is probably a dumb question, but I'm wondering how I would fill a list with the following data for a CSV file.
Here's the code so far,
class Info
{
[CsvColumn(Name = "Lease Name", FieldIndex = 1)]
public string leaseName2 { get; set; }
[CsvColumn(Name = "Field Name", FieldIndex = 2)]
public string fieldName2 { get; set; }
[CsvColumn(Name = "Reservoir", FieldIndex = 3)]
public string reservoir2 { get; set; }
[CsvColumn(Name = "Operator", FieldIndex = 4)]
public string operator2 { get; set; }
[CsvColumn(Name = "County", FieldIndex = 5)]
public string county2 { get; set; }
[CsvColumn(Name = "State", FieldIndex = 6)]
public string state2 { get; set; }
[CsvColumn(Name = "Majo", FieldIndex = 7)]
public string majo2 { get; set; }
[CsvColumn(Name = "Resv Cat", FieldIndex = 8)]
public string resvCat2 { get; set; }
[CsvColumn(Name = "Discount Rate", FieldIndex = 9)]
public double disRate2 { get; set; }
There are more columns I just did not want to list them all because that would be redundant. If anyone could help that would be greatly appreciated.
#GertArnold is right, you can use Union(). I used Concat() in my example. Union returns distinct records, Concat doesn't.
using System;
using System.Collections.Generic;
using System.Linq;
using LINQtoCSV;
namespace LinqCsvSandbox
{
class SampleData
{
[CsvColumn(Name = "ID", FieldIndex = 1)]
public string ID { get; set; }
[CsvColumn(Name = "PersonName", FieldIndex = 2)]
public string Name { get; set; }
public override string ToString()
{
return string.Format("{0}: {1}", ID, Name);
}
}
class Program
{
static void Main(string[] args)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = false,
FileCultureName = "en-us",
EnforceCsvColumnAttribute = true,
};
CsvContext cc = new CsvContext();
IEnumerable<SampleData> data1 = cc.Read<SampleData>("File1.csv", inputFileDescription);
IEnumerable<SampleData> data2 = cc.Read<SampleData>("File2.csv", inputFileDescription);
IEnumerable<SampleData> all = data1.Concat(data2);
// Uncomment to see the items printed
//foreach(var item in all)
// Console.WriteLine(item);
cc.Write(all, "All.csv");
}
}
}
In my example, File1.csv contains
1,Fred
2,Wilma
File2.csv contains
3,Tango
4,Cash
And the resulting All.csv contains
ID,PersonName
1,Fred
2,Wilma
3,Tango
4,Cash
For those unfamiliar, Linq to CSV is available as a package from NuGet.
Here are a couple of ways, the first is just using an object initializer, the second transfers a small list to the your CSV list:
List<Info> infoList = new List<Info>
{
new Info()
{
leaseName2 = "x"
}
};
List<string> stringList = new List<string> {"xy", "zz"};
infoList.AddRange(stringList.Select(stringVal => new Info()
{
leaseName2 = stringVal
}));