We have a fixed output record format where all fields are pipe-delimited. The layout is fixed, but the required fields are not in sequence (and this cannot be changed because customers are using the existing layout). Can we use CSVHelper and the Index Attribute to write out blank fields, e.g [Index(1)], [Index(4)], [Index(7)] would yield something like:
"Field1Value,,,Field2Value,,,Field3Value"?
We may eventually add the missing fields, but will have to consider this in a phased manner (because of customer implications), but want to be able to create the minimal output record format with the required field values.
We are working with .Net Core 2.2/C# for our efforts.
I looked at several nuget and other libraries that support decorating properties with data attributes like [Column(xx)] and [FieldOrder(xx)], but settled on CSVHelper because it seems robust and widely used.
public class Person
{
[Index(1)]
public string Name { get; set; }
[Index(4)]
public short Age { get; set; }
[Index(7)]
public string StreetAddress { get; set; }
}
static void Main(string[] args)
{
var records = new List<Person>
{
new Person { Name = "Jon Doe", Age = 100, StreetAddress = "123 Market Place" }
};
using (var writer = new StreamWriter("C:\\Projects\\CsvHelperDemo\\file.csv"))
using (var csv = new CsvWriter(writer))
{
csv.WriteRecords(records);
}
}
Expected:
Name,,,Age,,,StreetAddress
Jon Doe,,,100,,,123 Market Place
Actual:
Name,Age,StreetAddress
Jon Doe,100,123 Market Place
If it's an option, I would just add the properties to the Person
class. If they're not populated then you'll just have column names with no data beneath them. That's a little odd, but not much different from having "blank" column names and empty columns.
But if you wanted to get exactly the result you're describing, you could do this:
public class Person
{
[Index(0)]
public string Name { get; set; }
[Index(3)]
public short Age { get; set; }
[Index(6)]
public string StreetAddress { get; set; }
#region For spacing only
[Name("")]
[Index(1)]
public string Abc { get; set; }
[Name("")]
[Index(2)]
public string Xyz { get; set; }
[Name("")]
[Index(4)]
public string Foo { get; set; }
[Name("")]
[Index(5)]
public string Blarg { get; set; }
#endregion
}
I used bogus names. You could use the real ones instead. It doesn't matter because the column header will be blank.
One downside is that you could start populating one of those extra properties that you're not using now, and then the column would get populated without the header unless you remember to go back and remove the [Name(""}]
attribute.
You could also create a separate inherited class without changing Person
. That way you're populating Person
, and then you map it to the inherited class and use that for writing:
public class PersonWithExtraFields : Person
{
[Name("")]
[Index(1)]
public string Abc { get; set; }
[Name("")]
[Index(2)]
public string Xyz { get; set; }
[Name("")]
[Index(4)]
public string Foo { get; set; }
[Name("")]
[Index(5)]
public string Blarg { get; set; }
}
That's a literal answer describing how to do it with CsvHelper. Here's an easier way:
public class PersonCsvWriter
{
public void Write(List<Person> people, string path)
{
using (var file = new StreamWriter(path))
{
file.WriteLine("Name,,,Age,,,StreetAddress");
people.ForEach(p => file.WriteLine($"{p.Name},,,{p.Age},,,{p.StreetAddress}"));
}
}
}
I had the exact same problem. This is my solution:
public class Person
{
[Index(1)]
public string Name { get; set; }
[Index(4)]
public short Age { get; set; }
[Index(7)]
public string StreetAddress { get; set; }
}
static void Main(string[] args)
{
var records = new List<Person>
{
new Person { Name = "Jon Doe", Age = 100, StreetAddress = "123 Market Place" }
};
using (var writer = new StreamWriter("C:\\Projects\\CsvHelperDemo\\file.csv"))
using (var csv = new CsvWriter(writer))
{
csv.Context.RegisterClassMap<DynamicMap<Person>>();
csv.WriteRecords(records);
}
}
internal sealed class DynamicMap<T> : ClassMap<T>
{
public DynamicMap()
{
AutoMap(CultureInfo.InvariantCulture);
var properties = typeof(T).GetProperties();
var indexAttributes = properties.SelectMany(x => x.GetCustomAttributes(typeof(IndexAttribute), false))
.Cast<IndexAttribute>().ToList();
var maxIndex = indexAttributes.Max(x => x.Index);
for (int i = 0; i < maxIndex; ++i)
{
if (indexAttributes.All(x => x.Index != i))
{
Map().Index(i).Constant("");
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With