|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionCSV files are still found all over, and developer's often are faced with situations for parsing and manipulating that data. Often times, we want to take the CSV data and use it to initialize objects. In this article, we'll take a look at one approach to mapping incoming CSV data to our own objects. For brevity, I will assume that you have already developed a way to parse a given CSV input line and convert it to an array of strings. BackgroundI was first prompted to look at this problem when I was asked by a customer if there was an easy way to map incoming CSV data to objects. He had already figured out how to use regular expressions to parse the line of text he read into his application to create an array containing all the fields from the data file. It really was a matter of then creating objects from that array. The obvious and brute force method would be something like this: Customer customerObj = new Customer();
customerObj.Name = datafields[0];
customerObj.DateOfBirth = DateTime.Parse(datafields[1]);
customerObj.Age = int.Parse(datafields[2]);
That would be fairly straightforward, but with more than a few objects or properties, it would get pretty tedious. And, there is no accounting for any custom processing of the input data prior to assigning it to fields. You could also come up with a special constructor for each class that would take an array of data and set the object up correctly, which would probably be a marginally better approach. The ApproachMy initial two thoughts when faced with this problem were:
With those two thoughts in mind (and thereby limiting my other remaining thoughts to one, since I can only manage three things at a time), I set out to free up my thought queue as fast as possible. From those two thoughts, I picked three key things that drove my thinking:
Creating the Custom AttributeI started tackling this idea by working backwards on my list. First, I needed a .NET attribute I could use. If you have never worked with custom attributes, they are pretty cool, though they almost always lead to using Reflection. I think many developers get scared off by Reflection for whatever reason (since you don’t see it used in a lot of scenarios where it would make life a ton easier), and that is a shame. Reflection, really, is straightforward, so make sure it is part of your toolbox. To create a custom attribute, you just need to define a class that inherits from [AttributeUsage(AttributeTargets.Property)]
public class CSVPositionAttribute : System.Attribute
{
public int Position;
public CSVPositionAttribute(int position)
{
Position = position;
}
public CSVPositionAttribute()
{
}
}
In this case, the user will need to supply a To use this custom property, all I would have to do is the following: public class SomeClass
{
private int _age;
[CSVPosition(2)]
public int Age
{
get { return _age;}
set {_age = value;}
}
}
The
Creating the Loader with ReflectionThe next step, is to have a way to figure out how to take some arbitrary class, figure out which properties are to be populated with data from a CSV, and update the object with that data. To do that, I will use .NET Reflection. I start by creating a new class called public class ClassLoader
{
public static void Load(object target,
string[] fields, bool supressErrors)
{
}
}
The The first thing I am going to need to do is evaluate the incoming object for all of its available properties and check those properties for the Type targetType = target.GetType();
PropertyInfo[] properties = targetType.GetProperties();
I can then iterate over the foreach (PropertyInfo property in properties)
{
// Make sure the property is writeable (has a Set operation)
if (property.CanWrite)
{
// find CSVPosition attributes assigned to the current property
object[] attributes =
property.GetCustomAttributes(typeof(CSVPositionAttribute),
false);
// if Length is greater than 0 we have
// at least one CSVPositionAttribute
if (attributes.Length > 0)
{
// We will only process the first CSVPositionAttribute
CSVPositionAttribute positionAttr =
(CSVPositionAttribute)attributes[0];
//Retrieve the postion value from the CSVPositionAttribute
int position = positionAttr.Position;
try
{
// get the CSV data to be manipulate
// and written to object
object data = fields[position];
// set the value on our target object with the data
property.SetValue(target,
Convert.ChangeType(data, property.PropertyType), null);
}
catch
{
// simple error handling
if (!supressErrors)
throw;
}
}
}
}
You should be able to figure out what is going on, by reading the comments above. Basically, we check each property to see if we can write to it, and if we can, we see if it has a Implementing Data TransformsYou may also wonder why the following line of code was used in our // get the CSV data to be manipulate and written to object
object data = fields[position];
Couldn’t we just as easily pass the Once again, .NET Reflection will ride to the rescue. Using .NET Reflection, we can call methods on a given object dynamically, even if we don’t know what the names of those methods are at design time. So, the question quickly becomes – how do we let our processing routine know that:
We will solve both problems by extending our Our new [AttributeUsage(AttributeTargets.Property)]
public class CSVPositionAttribute : System.Attribute
{
public int Position;
public string DataTransform = string.Empty;
public CSVPositionAttribute(int position,
string dataTransform)
{
Position = position;
DataTransform = dataTransform;
}
public CSVPositionAttribute(int position)
{
Position = position;
}
public CSVPositionAttribute()
{
}
}
As you can see, all we have done is add a new public field named try
{
// get the CSV data to be manipulate and written to object
object data = fields[position];
// check for a Tranform operation that needs to be executed
if (positionAttr.DataTransform != string.Empty)
{
// Get a MethodInfo object pointing to the method declared by the
// DataTransform property on our CSVPosition attribute
MethodInfo method = targetType.GetMethod(positionAttr.DataTransform);
// Invoke the DataTransform method and get the newly formated data
data = method.Invoke(target, new object[] { data });
}
// set the ue on our target object with the data
property.SetValue(target, Convert.ChangeType(data,
property.PropertyType), null);
}
The code now checks for a The last thing I did was add an additional method to my public static X LoadNew<X>(string[] fields, bool supressErrors)
{
// Create a new object of type X
X tempObj = (X) Activator.CreateInstance(typeof(X));
// Load that object with CSV data
Load(tempObj, fields, supressErrors );
// return the new instanace of the object
return tempObj;
}
Using the CodeHere is a brief example of how to use this code. I have a class Customer
{
private string _name;
private string _title;
private int _age;
private DateTime _birthDay;
[CSVPosition(2)]
public string Name
{
get { return _name; }
set { _name = value; }
}
[CSVPosition(0,"TitleFormat")]
public string Title
{
get { return _title; }
set { _title = value; }
}
[CSVPosition(1)]
public int Age
{
get { return _age; }
set { _age = value; }
}
[CSVPosition(3)]
public DateTime BirthDay
{
get { return _birthDay; }
set { _birthDay = value; }
}
public Customer()
{
}
public string TitleFormat(string data)
{
return data.Trim().ToUpper();
}
public override string ToString()
{
return "Customer object [" + _name + " - " +
_title + " - " + _age + " - " + _birthDay + "]";
}
}
Populating this class with data using the static void Main(string[] args)
{
string[] fields = { " Manager", "38", "John Doe", "4/1/68" };
Customer customer1 = new Customer();
ClassLoader.Load(customer1, fields, true);
Console.WriteLine(customer1.ToString());
Customer customer2 = ClassLoader.LoadNew<Customer>(fields,false);
Console.WriteLine(customer2.ToString());
Console.ReadLine();
}
That is all there is to it. Hope it helps, and happy coding.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||