First of all, I wanna say: "I know there're XML/JSON/YAML formats, and I know how they works". But now I'm facing a task to make export to CSV format file.
I've read about CSV on wikipedia, searched StackOverflow on CSV topics, and didn't find answer.
As I read, this is popular format for future Excel tables display.
Okay, if I have a simple class with only ValueType properties, it's just fine.
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
public string ToCsvString()
{
return string.Format("{0};{1}", ID, Name);
}
public static MyClass FromCsvString(string source)
{
var parts = source.Split(';');
var id = int.Parse(parts[0]);
var name = parts[1];
return new MyClass()
{
ID = id,
Name = name,
};
}
}
But what if I have a little bit more complex class. For example with List<> of other objects.
public class MyClassWithList: MyClass
{
public MyClassWithList()
{
ItemsList = new List<string>();
}
public List<string> ItemsList { get; set; }
public string ToCsvString()
{
// How to format it for future according to CSV format?
return string.Format("{0};{1}", base.ToCsvString(), ItemsList.ToString());
}
public static MyClassWithList FromCsvString(string source)
{
var parts = source.Split(';');
var id = int.Parse(parts[0]);
var name = parts[1];
// How to get it back from CSV formatted string?
var itemsList = parts[2];
return new MyClassWithList()
{
ID = id,
Name = name,
ItemsList = new List<string>()
};
}
}
How should I serialize/deserialize it to CSV?
And final question is how to do the same about when class A contains class B instances?
First off, you have to flatten your data.
If ClassA contains a ClassB, then you'll need to create a flattened POCO that has properties that access any nested properties, e.g. ClassB_PropertyA.
You can really only have 1 variable length property and it has to be the last property, then you can have any column after a point represent a single list property.
Secondly, there is no CSV Serliazation standard. There is https://www.ietf.org/rfc/rfc4180.txt but that only deals with reading text from fields. Something as simple as changing your locale can mess up a CSV library as semicolons will be switched for commas in cultures where a common represents a decimal. There are also many bugs and edge cases in Excel that cause serialization to String to be problematic. And some data is automatically converted to Dates or Times. You need to determine which program you expect to open the CSV and learn about how it handles CSV data.
Once you have a flat POCO, then a CSV is simply a header row with the name of each property followed by a row per object. There are libraries that can help you with this.
Related
I have some JSON data, which I wish to deserialize into a C# object.
The data looks like this:
{
"version": 0,
"guid": "2166a7d5744d47009adaa29f0e549696",
"csv": "one:1,two:2,three:3",
"method": "substract"
}
The "csv" property is giving me trouble, as I want it to be deserialized to the following csv object (it's content can change, so it can have more or less comma-separated values):
public class CSV
{
public int One {get;set;}
public bool Two {get;set;}
public int Three {get;set;}
}
I already have an object for the bigger JSON:
public class MyJsonData
{
public int Version {get;set;}
public string Guid {get;set;}
public string Csv {get;set;}
public string Method {get;set;}
}
But I just can't figure out how to parse the comma-separated string to my CSV object.
I tried adding a public CSV (string s) constructor to the CSV object and changing the type from string to CSV in the MyJsonData object, hoping C# would use that constructor (I'm using System.Text.Json) but it didn't and it threw an exception.
The only thing I can come up is separating the string by commas and using reflection to fill the CSV object properties (I've defined all possible values the string can have and expect the missing ones to be empty or have the default value for the type). But I'm looking for a more elegant solution, as I am accessing the MyJsonData object everywhere in my code, so having to separately handle a CSV object seems cumbersome and a worst-practice.
Is there any way to tell JSON what method to use to convert the string to my CSV object when deserializing?
If you use Newtonsoft.Json you will need only one more string of code
using Newtonsoft.Json;
MyJsonData myJsonData = JsonConvert.DeserializeObject<MyJsonData>(json);
public class MyJsonData
{
public CSV Csv { get; set; }
//...another properties
[JsonConstructor]
public MyJsonData(string csv)
{
Csv = new JObject(csv.Split(",")
.Select(i => i.Split(":"))
.Select(r => new JProperty(r[0].ToString(), Convert.ToInt32(r[1]))))
.ToObject<CSV>();
}
}
If you use Text.Json with Net 6+, the shortest code probably is
var jDoc = JsonNode.Parse(json);
jDoc["csv"] = new JsonObject( JsonDocument.Parse(json).RootElement
.GetProperty("csv").GetString()
.Split(",")
.Select(r => r.Split(":") )
.Select(i=> KeyValuePair.Create<string, JsonNode>(i[0], Convert.ToInt32(i[1]))));
MyJsonData myJsonData = jDoc.Deserialize<MyJsonData>( new JsonSerializerOptions {PropertyNameCaseInsensitive = true});
if you want one line of code you will probably wrap this code in a custom formater ( as it is usually in the most cases when you use Text.Json)
UPDATE
if some of CSV properties are boolean, you can try this code
Csv = new JObject(csv.Split(",")
.Select(i => i.Split(":"))
.Select(r => new JProperty(r[0].ToString(),
r[1].ToLower() =="true"||r[1].ToLower() =="false"? Convert.ToBoolean( r[1]):
Convert.ToInt32(r[1]))))
.ToObject<CSV>();
I want to create a method that displays the information contained in an object, that will work dynamically, with any object. I'm having trouble handling properties that are other custom classes. In the example below the Person has Phones and Occupations which both are other classes. When the data is displayed, the value on the screen currently is:
TestReflection.Person
Name: Mary
Phones: TestReflection.Phones
Occupations: TestReflection.Occupations
It just displays the name of class, like TestReflection.Phones, rather than the data inside that object.
How can I change this code to show information like this instead?
TestReflection.Person
Name: Mary
Phones:
TestReflection.Phones
Type: 1
Number: 555XYZ
Occupations:
TestReflection.Occupations
Type: 5
Description: Secretary
Here is my code:
class Program
{
static void Main(string[] args)
{
List<Person> listPeson = new List<Person>();
var person1 = new Person();
person1.Name = "Mary";
person1.Phones = new Phones { new Phone { Type = 1, Number = "555XYZ" } };
person1.Occupations = new Occupations {new Occupation { Type = 5, Description = "Secretary" }};
listPeson.Add(person1);
DynamicExport(listPeson);
Console.ReadLine();
}
public static void DynamicExport<T>(List<T> listReg)
{
for (int i = 0; i < listReg.Count; i++)
{
Console.WriteLine(listReg[i].GetType());
foreach (var item in listReg[i].GetType().GetProperties())
{
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
}
}
}
}
class Person
{
public string Name { get; set; }
public Phones Phones { get; set; }
public Occupations Occupations { get; set; }
}
class Phones : List<Phone> { }
class Phone
{
public int Type { get; set; }
public string Number { get; set; }
}
class Occupations : List<Occupation> { }
class Occupation
{
public int Type { get; set; }
public string Description { get; set; }
}
I made some edits to your question - I hope I understood you correctly.
If you want to export data
If your question is really about displaying data, then there are better ways to do it than creating your own export method. The format you are trying to display looks similar to YAML. There's also JSON and XML. Using one of these libraries is probably better than writing your own method:
YamlDotNet NuGet package
Json.NET NuGet Package
System.Xml.Serialization.XmlSerializer class
If you want to learn more about reflection
Maybe you're interested in learning more about reflection, and the export is just an example to play around with it. In that case, let's look at this line:
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
$"{item.GetValue(listReg[i], null)}" ends up calling person1.Phones.ToString(). The default behavior of ToString just displays the type name. You could override that behavior, like this:
class Phones : List<Phone>
{
public override string ToString()
{
return Program.DynamicExportToString(this);
// ... where DynamicExportToString is a modified version of DynamicExport that
// builds and returns a string rather than sending it directly to the Console.
}
}
Maybe you want to be able to handle any class, even when you cannot override ToString in all of the classes you might export. Then you will need to put some additional logic in the DynamicExport method, because...
$"{item.Name}: {item.GetValue(listReg[i], null)}"
... doesn't work for every situation. We need to display different things depending on the type of the property.
Consider how you want to handle null values. Maybe something like $"{item.Name}: <null>"
Use your existing $"..." code if the type is...
a primitive type.
DateTime
String
... or a Nullable<> of one of those types.
If the type implements IEnumerable, loop over the contents of the collection and recursively call your export code for each element.
It's important to check for this interface after you've checked if the type is a String, because String implements IEnumerable.
Otherwise, recursively call your export code on this value.
When you call your export code recursively, it would be wise to guard against infinite loops. If the object you're trying to export contains a circular reference - you could quickly wind up with a StackOverflowException. To avoid this, maintain a stack of objects that have already been visited.
I think the above advice is generally applicable whenever you're using reflection to traverse an object graph - whether it's for serialization or any other purpose.
I hope this helps!
So after about an hour's worth of pulling my hair in desperation, I decided to follow the advice from, like, everybody in here and not implement my own CSV-parser.
So I went with FileHelpers instead.
But I am having a bit of trouble using it correctly.
My CSV-file looks something like this:
50382018,50319368,eBusiness Manager,IT02,3350_FIB4,IT,2480
50370383,50373053,CRM Manager,IT01,3200_FIB3,xyz,2480
50320067,50341107,"VP, Business Information Officer",IT03,3200_FI89,xyz,2480
50299061,50350088,Project Expert,IT02,8118_FI09,abc,2480
My need for FileHelpers (and, specifically CsvEngine) is in line 3 - notice third column enclosed in quotes since it has an internal comma (which is otherwise used as delimiter).
My code to read the file is this:
var co = new FileHelpers.Options.CsvOptions("Employee", columnDeliminator, 7);
var ce = new CsvEngine(co);
var records = ce.ReadFile(pathToCSVFile);
It works fine - sort of. It correctly parses the lines and recognizes the values with enclosed delimiters.
But.
The return value of the ReadFile()-method is object[]. And the contents of it appears to be some kind of dynamic type.
It looks something like this - where the columns are named "Field_1", "Field_2" etc.
I have created a "data class" intended to hold the parsed lines It looks like this:
public class Employee
{
public string DepartmentPosition;
public string ParentDepartmentPosition;
public string JobTitle;
public string Role;
public string Location;
public string NameLocation;
public string EmployeeStatus;
}
Is there a way to have FileHelpers' CsvEngine class to return strongly typed data?
If I could just use the "basic" parser of FileHelpers, I could use this code:
var engine = new FileHelperEngine<Employee>();
var records = engine.ReadFile("Input.txt");
Is there a way to have CsvEngine return instances of my "Employee" class? Or do I have to write my own mapping code to support this?
#shamp00 has the correct answer - and I also found it at FileHelper escape delimiter .
I took my model class and decorated each property on it as suggested:
(I probably don't need to decorate all properties, but it works for now)
[DelimitedRecord((","))]
public class Employee
{
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string DepartmentPosition;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string ParentDepartmentPosition;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string JobTitle;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string Role;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string Location;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string NameLocation;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string EmployeeStatus;
}
Now I just need this code:
TextReader reader = new StreamReader(contents);
var engine = new FileHelperEngine<Employee>()
{
Options = { IgnoreFirstLines = 1 }
};
var myRecords = engine.ReadStream(reader);
The documentation worked for me for a one simple way:
First in your class, it needs a couple decorators:
Edit Use the FieldQuoted decorator to parse anything in quotes and ignore the included comma
[DelimitedRecord(",")]
class Person
{
[FieldQuoted]
public string Name { get; set; }
[FieldConverter(ConverterKind.Int32)]
public int Age { get; set; }
public string State { get; set; }
}
DelimitedRecord for the class and the expected delimiter (this could be a problem if things change later.
and FieldConverter for it appears anything other than string.
Then change your reading method slightly:
var fhr = new FileHelperEngine<Person>();
var readLines = fhr.ReadFile(pathToFile);
and then it works, strongly typed:
foreach(var person in readLines)
{
Console.WriteLine(person.Name);
}
Using CsvHelper as a viable alternative and assuming the CSV file has no headers,
a mapping can be created for the Employee class like
public sealed class EmployeeClassMap : ClassMap<Employee> {
public EmployeeClassMap() {
Map(_ => _.Location).Index(0);
Map(_ => _.NameLocation).Index(1);
Map(_ => _.JobTitle).Index(2);
//...removed for brevity
}
}
Where the index is mapped to a respective property on the strongly typed object model.
To use this mapping, you need to register the mapping in the configuration.
using (var textReader = new StreamReader(pathToCSVFile)) {
var csv = new CsvReader(textReader);
csv.Configuration.RegisterClassMap<EmployeeClassMap>();
var records = csv.GetRecords<Employee>();
//...
}
If this lib not work, you can also try to use built-in .Net CSV parser TextFieldParser. For ex: https://coding.abel.nu/2012/06/built-in-net-csv-parser/
ADDED:
For types (with auto convert):
static void run()
{
// split with any lib line of CSV
string[] line = new string[]{"john", "doe", "201"};
// needed prop names of class
string[] propNames = "fname|lname|room".Split('|');
Person p = new Person();
parseLine<Person>(p, line, propNames);
}
static void parseLine<T>(T t, string[] line, string[] propNames)
{
for(int i = 0;i<propNames.Length;i++)
{
string sprop = propNames[i];
PropertyInfo prop = t.GetType().GetProperty(sprop);
object val = Convert.ChangeType(line[i], prop.PropertyType);
prop.SetValue(t, val );
}
}
class Person
{
public string fname{get;set;}
public string lname{get;set;}
public int room {get;set;}
}
I'm trying to put multiple values (arrays) in a key, but I do not know how to do this.I've tried:
public class Customer
{
public string Name { get; set; }
public string Bought[5] { get; set; }
}
List<Customer> Products = new List<Customer>();
Customer product = new Customer();
product.Bought[0] = listproducts.SelectedIndex;
product.Bought[1] = listproducts.SelectedIndex;
product.Bought[3] = listproducts.SelectedIndex;
product.Bought[4] = listproducts.SelectedIndex;
product.Bought[5] = listproducts.SelectedIndex;
I know this is wrong, but is a poor example for what I'm trying to do.
I was trying something by this way:
Store multiple values in single key in json
But I do not know how to adapt to C#.
I just need to save to the JSON file something like:
[{"Name":"Bryan", "Products":"car", "boat", "bike"}]
If someone is able to help me, I would be grateful. I would give this further step toward knowledge.
You should just use a List<string> for Bought, or List<BoughtObject>.
That will create
{ // Customer
Name : "Bryan",
Bought: [// Array of your bought objects
{ ... },
{ ... }
]
}
If you just want it to be a single string comma separated (and then split it on the client side or display it like "Boat, Jet, Tv" you could leave it as type of string and then do a .Join(boughtItems)
You could use something like Dictionary<Type1, List<type2> >. In your case you could do Dictionary<string, List<string> >. That way you will able to store multiple values for a key.
Try with this may be it will work
public class Customer
{
public string Name { get; set; }
public readonly string[] Bought = new string[5];
}
This is a very complicated question concerning how to serialize data via a web service call, when the data is not-strongly typed. I'll try to lay it out as best possible.
Sample Storage Object:
[Serializable]
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public List<NameValuePairs> OtherInfo { get; set; }
}
[Serializable]
public class NameValuePairs {
public string Name { get; set; }
public string Value { get; set; }
}
Sample Use:
[WebMethod]
public List<StorageObject> GetStorageObjects() {
List<StorageObject> o = new List<StorageObject>() {
new StorageObject() {
Name = "Matthew",
Birthday = "Jan 1st, 2008",
OtherInfo = new List<NameValuePairs>() {
new NameValuePairs() { Name = "Hobbies", Value = "Programming" },
new NameValuePairs() { Name = "Website", Value = "Stackoverflow.com" }
}
},
new StorageObject() {
Name = "Joe",
Birthday = "Jan 10th, 2008",
OtherInfo = new List<NameValuePairs>() {
new NameValuePairs() { Name = "Hobbies", Value = "Programming" },
new NameValuePairs() { Name = "Website", Value = "Stackoverflow.com" }
}
}
};
return o;
}
Return Value from Web Service:
<StorageObject>
<Name>Matthew</Name>
<Birthday>Jan 1st, 2008</Birthday>
<OtherInfo>
<NameValuePairs>
<Name>Hobbies</Name>
<Value>Programming</Value>
</NameValuePairs>
<NameValuePairs>
<Name>Website</Name>
<Value>Stackoverflow.com</Value>
</NameValuePairs>
</OtherInfo>
</StorageObject>
What I want:
<OtherInfo>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</OtherInfo>
The Reason & Other Stuff:
First, I'm sorry for the length of the post, but I wanted to give reproducible code as well.
I want it in this format, because I'm consuming the web services from PHP. I want to easily go:
// THIS IS IMPORANT
In PHP => "$Result["StorageObject"]["OtherInfo"]["Hobbies"]".
If it's in the other format, then there would be no way for me to accomplish that, at all. Additionally, in C# if I am consuming the service, I would also like to be able to do the following:
// THIS IS IMPORANT
In C# => var m = ServiceResult[0].OtherInfo["Hobbies"];
Unfortunately, I'm not sure how to accomplish this. I was able to get it this way, by building a custom Dictionary that implemented IXmlSerializer (see StackOverflow: IXmlSerializer Dictionary), however, it blew the WSDL schema out of the water. It's also much too complicated, and produced horrible results in my WinFormsTester application!
Is there any way to accomplish this ? What type of objects do I need to create ? Is there any way to do this /other than by making a strongly typed collection/ ? Obviously, if I make it strongly typed like this:
public class OtherInfo {
public string Hobbies { get; set; }
public string FavoriteWebsite { get; set; }
}
Then it would work perfectly, I would have no WSDL issues, I would be able to easily access it from PHP, and C# (.OtherInfo.Hobbies).
However, I would completely lose the point of NVP's, in that I would have to know in advance what the list is, and it would be unchangeable.. say, from a Database.
Thanks everyone!! I hope we're able to come up with some sort of solution to this. Here's are the requirements again:
WSDL schema should not break
Name value pairs (NVP's) should be serialized into attribute format
Should be easy to access NVP's in PHP by name ["Hobbies"]
Should be easy to access in C# (and be compatible with it's Proxy generator)
Be easily serializable
Not require me to strongly type the data
Now, I am /completely/ open to input on a better/different way to do this. I'm storing some relatively "static" information (like Name), and a bunch of pieces of data. If there's a better way, I'd love to hear it.
This is like dynamic properties for a object.
C# is not quite a dynamic language unlike javascript or maybe PHP can parse the object properties on the fly. The following two methods are what I can think of. The second one might fit into your requirements.
The KISS Way
The Keep It Simple Stupid way
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public List<string> OtherInfo { get; set; }
}
You can have name value pairs which is separated by '|'
OtherInfo = {"Hobbies|Programming", "Website|Stackoverflow.com"}
Serialized forms
<StorageObject>
<Name>Matthew</Name>
<Birthday>Jan 1st, 2008</Birthday>
<OtherInfo>
<string>Hobbies|Programming</string>
<string>Website|Stackoverflow.com</string>
</OtherInfo>
</StorageObject>
The Dynamic Way in C#
Make the name value pair part become an XML element so that you can build it dynamically.
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public XElement OtherInfo { get; set; } // XmlElement for dot net 2
}
You can easily build up OtherInfo object as element centric
e.g.
XElement OtherInfo = new XElement("OtherInfo");
OtherInfo.Add( ..Hobbies xelement & text value..);
OtherInfo.Add( ..WebSite xelement & text value..);
The serialized form will be
<OtherInfo>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</OtherInfo>
or build it as attribute centric
XElement OtherInfo = new XElement("OtherInfo");
OtherInfo.Add( ..nvp xattribute Hobbies & value..);
OtherInfo.Add( ..nvp xattribute WebSite & value..);
<OtherInfo>
<nvp n="Hobbies" v="Programming" />
<nvp n="Website" v="Stackoverflow.com" />
</OtherInfo>
For any dynamic language, it can access to the properties directly.
For the rest, they can access the value by read the XML. Reading XML is well supported by most of framework.
This is what I've settled on.
Class Structure:
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
[XmlAnyElement("Info")] // this prevents double-nodes in the XML
public XElement OtherInfo { get; set; }
}
Usage:
StorageObject o = new StorageObject();
o.OtherInfo.Add(new XElement("Hobbies","Programming");
o.OtherInfo.Add(new XElement("Website","Stackoverflow.com");
Output:
<Info>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</Info>
I would like to thank everyone for their assistance, I really appreciate the help and ideas.
As a completely different take on this, why not think about doing it completely differently. Have one web service method to return the serialized storage object, minus the OtherInfo and another method to return the list of properties (keys) for OtherInfo, and a third to return the list of values for any key. Granted, it will take more round trips to the web service if you want all of the data, but the solution will be much simpler and more flexible.
[Serializable]
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
[Nonserializable]
public Dictionary<string,List<string>> OtherInfo { get; set; }
}
[WebMethod]
public List<StorageObject> GetStorageObjects() {
// returns list of storage objects from persistent storage or cache
}
[WebMethod]
public List<string> GetStorageObjectAttributes( string name )
{
// find storage object, sObj
return sObj.Keys.ToList();
}
[WebMethod]
public List<string> GetStorageObjectAtributeValues( sting name, string attribute )
{
// find storage object, sObj
return sObj[attribute];
}
Have a look into the System.Xml.Serialization.XmlSerializerAssemblyAttribute attribute. This lets you specify a custom class-level serializer. You'll be able to spit out whatever XML you like.
A quick way to get up to speed on these is to use sgen.exe to generate one and have a peek at it with Reflector.
-Oisin
I'm not sure this would solve your problem (it would in C#, but maybe not in PHP), but try using Dictionary<string,List<string>> OtherInfo instead of List<NameValuePairs>. Then "Hobbies" and "Websites" would be your keys and the values would be the list of hobbies or web sites. I'm not sure how it would serialize, though.
You would be able to reference the lists of hobbies as:
List<string> hobbies = storageObject.OtherInfo["Hobbies"];
[EDIT] See here for a generic XML serializable dictionary. This derived class is the one you would need to use instead of generic Dictionary.