Reputation: 6492
I am reading lines from a text file. each line is pipe separated. I want to validate the line i.e each item is according to a specified format after splitting by delimiter. eg:- Here is my line
D|111111|87654321|Bar|BCreace|GBP|24/08/2010
Check if the above line follows the following format for eg:
Field Ref Field Length
S0 1
S1 6
S2 34
...
S6 10
Currently i am using if condition as below:
var sortCode = 0;
if (!int.TryParse(items[1], out sortCode) || items[1].Length > 6)
errorMessage.Add("Sort Code", "Invalid sort code");
But can anyone help me if this can be done in a proper way?
Thanks
Upvotes: 1
Views: 1245
Reputation: 71227
Initializing sortCode
to 0
is redundant, because an out
parameter is guaranteed by the compiler to be initialized by the TryParse
function... and the default value for an int
value type is 0 anyway.
So instead of this:
var sortCode = 0;
You could have this:
int sortCode;
if (!int.TryParse(items[1], out sortCode) || items[1].Length > 6)
6
is a magic number here, it would be better to make it a constant with a meaningful name. Also, there might be a bit too much logic crammed into that one line. How about this?
var parsed = int.TryParse(items[1], out sortCode);
if (!parsed || items[1].Length > SORTCODE_LENGTH)
{
errorMessage.Add("Sort Code", "Invalid sort code");
}
Depending on context, throwing an exception might be a better idea here - if your method is doing more than what you've shown, it's probably doing too many things and could benefit from another abstraction level.
Notice the explicit scope under the if
- for an example of what can happen with implicit scopes, try searching for "Apple SSL goto fail" ;-)
If this is the kind of answer you're looking for, you need to try Code Review.
Upvotes: 1
Reputation: 18744
There is no one proper way of doing this but I have a suggestion for you that uses a custom attribute.
I named it ColumnInfoAttribute
:
class ColumnInfoAttribute : Attribute
{
public int Index { get; set; }
public int MaxLength { get; set; }
}
It allows you to specify the index and max length of a field so you can apply it all properties that should receive a value:
class LineItem
{
[ColumnInfo(Index = 0, MaxLength = 1)]
public string S0 { get; set; }
[ColumnInfo(Index = 1, MaxLength = 6)]
public string S1 { get; set; }
[ColumnInfo(Index = 2, MaxLength = 34)]
public string S2 { get; set; }
[ColumnInfo(Index = 3, MaxLength = 34)]
public string S3 { get; set; }
[ColumnInfo(Index = 4, MaxLength = 34)]
public string S4 { get; set; }
[ColumnInfo(Index = 5, MaxLength = 34)]
public string S5 { get; set; }
[ColumnInfo(Index = 6, MaxLength = 34)]
public DateTime S6 { get; set; }
public static LineItem Parse(string line)
{
var propertyDictionary =
typeof(LineItem)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
// create an anonymous object to hold the property and the ColumnInfo
.Select(p => new
{
Property = p,
ColumnInfo = p.GetCustomAttribute<ColumnInfoAttribute>()
})
// get only those where the ColumnInfo is not null (in case there are other properties)
.Where(ci => ci.ColumnInfo != null)
// create a dictionary with the Index as a key
.ToDictionary(ci => ci.ColumnInfo.Index);
var result = new LineItem();
var values = line.Split('|');
for (var i = 0; i < values.Length; i++)
{
// validate the length of the value
var isValidLength = values[i].Length > propertyDictionary[i].ColumnInfo.MaxLength;
if (!isValidLength)
{
// todo: throw some appropriate exception or do other error handling
}
// set the corresponding property
var converterdValue = Convert.ChangeType(
values[i],
propertyDictionary[i].Property.PropertyType);
propertyDictionary[i].Property.SetValue(result, converterdValue);
}
return result;
}
}
The same class has also a Parse
method that via reflection gets all properties with the ColumnInfo
attribute and creates a dictionary. The key of the dictionary is the Index
.
Now you can for-loop over all values and use the i
to get a ColumnInfo
. You then check if the length of the field is valid and if so you use the property.SetValue
to assign a value to the property.
Usage:
var line = "D|111111|87654321|Bar|BCreace|GBP|24/08/2010";
var lineItem = LineItem.Parse(line);
It's easily extensible and very generic. If you have more such cases you can put this code in a base class and add the attributes to the derived classes.
Upvotes: 1
Reputation: 8208
How about a regex?
[^|]\|[^|]{6}\|[^|]{8}\|[^|]{3}\|[^|]{7}\|[^|]{3}\|[^|]{10}
Try it out here https://regex101.com/r/rI6zN6/1
Upvotes: -1
Reputation: 2532
This might be a little less performant but I find it easier to read and maintain. You can parse the line to a strongly typed object then validate the object. In the example below I am using FluentValidation.
public class LineItem
{
public static LineItem Parse(string line)
{
var split = line.Split('|');
return new LineItem(split[0], split[1], split[2], split[3], split[4], split[5], split[6]);
}
public LineItem(string s0, string s1, string s2, string s3, string s4, string s5, string s6)
{
//any param value checks
S0 = s0;
S1 = s1;
S2 = s2;
S3 = s3;
S4 = s4;
S5 = s5;
S6 = s6;
}
public string S0 { get; set; }
public string S1 { get; set; }
public string S2 { get; set; }
public string S3 { get; set; }
public string S4 { get; set; }
public string S5 { get; set; }
public string S6 { get; set; }
}
public class LineItemValidator : AbstractValidator<LineItem>
{
public LineItemValidator()
{
RuleFor(line => line.S0).Length(1);
RuleFor(line => line.S2).Length(6);
//etc
}
}
Then usage would be something like this:
public class FileValidatorTests
{
[Fact]
public void Spike()
{
var line = "D|111111|87654321|Bar|BCreace|GBP|24/08/2010";
var lineItem = LineItem.Parse(line);
var result = new LineItemValidator().Validate(lineItem);
Assert.True(result.IsValid);
}
}
Upvotes: 0