patterncsharpMinor
Parsing pipe delimited lines
Viewed 0 times
pipeparsinglinesdelimited
Problem
I am parsing the following line of code via a specific format.
Line is:
The Format is:
I am validating using many
```
private static StatementLineResult Validate(string delimitedRecord)
{
if (delimitedRecord == null)
throw new ArgumentNullException("delimitedRecord");
var items = delimitedRecord.Split('|');
if(items.Length != 19)
throw new Exception("Improper line format");
var errorMessage = new Dictionary();
var bankIdentifierCodes = new List {"ABCDGB2L", "EFGHGB2L"};
if (items[0].Length != 1 || items[0] != "S")
errorMessage.Add("Record Identifier","Invalid Record Identifier");
var sortCode = 0;
if (!int.TryParse(items[1], out sortCode) || items[1].Length > 6)
errorMessage.Add("Sort Code", "Invalid sort code");
if (string.IsNullOrEmpty(items[2]) || items[1].Length > 34)
errorMessage.Add("Account Number", "Invalid account number");
if (string.IsNullOrEmpty(items[3]) || items[1].Length > 35)
errorMessage.Add("Account Alias", "Invalid account alias");
if (string.IsNullOrEmpty(items[4]) || items[1].Length > 35)
errorMessage.Add("Account Name", "Invalid account name");
if(string.IsNullOrEmpty(items[5]) || items[5].Length != 3)
errorMessage.Add("Account Currency", "Invalid account currency");
if (!string.IsNullOrEmpty(items[6]) && items[6].Length > 20)
errorMessage.Add("Account Type", "Invalid account type");
if(string.IsNullOrEmpty(items[7]) || items[7].Length != 8 || !bankIdentifierCodes.Contains(items[7],StringComparer.OrdinalIgnoreCase))
errorMessage.Add("Bank Identifier Code", "Invalid bank identifier code");
if (!string.IsNullOrEmpty(items[8]) && items[8].Length > 35)
errorMessage.Add(
Line is:
S|111111|87654321|Bar UK|BCreace UK|GBP|24/08/2010|The Format is:
Index Field Length
S0 - 1
S1 - 6
S2 - 34
....
...
S6 - 10I am validating using many
if statements. Could anyone suggest a better approach?```
private static StatementLineResult Validate(string delimitedRecord)
{
if (delimitedRecord == null)
throw new ArgumentNullException("delimitedRecord");
var items = delimitedRecord.Split('|');
if(items.Length != 19)
throw new Exception("Improper line format");
var errorMessage = new Dictionary();
var bankIdentifierCodes = new List {"ABCDGB2L", "EFGHGB2L"};
if (items[0].Length != 1 || items[0] != "S")
errorMessage.Add("Record Identifier","Invalid Record Identifier");
var sortCode = 0;
if (!int.TryParse(items[1], out sortCode) || items[1].Length > 6)
errorMessage.Add("Sort Code", "Invalid sort code");
if (string.IsNullOrEmpty(items[2]) || items[1].Length > 34)
errorMessage.Add("Account Number", "Invalid account number");
if (string.IsNullOrEmpty(items[3]) || items[1].Length > 35)
errorMessage.Add("Account Alias", "Invalid account alias");
if (string.IsNullOrEmpty(items[4]) || items[1].Length > 35)
errorMessage.Add("Account Name", "Invalid account name");
if(string.IsNullOrEmpty(items[5]) || items[5].Length != 3)
errorMessage.Add("Account Currency", "Invalid account currency");
if (!string.IsNullOrEmpty(items[6]) && items[6].Length > 20)
errorMessage.Add("Account Type", "Invalid account type");
if(string.IsNullOrEmpty(items[7]) || items[7].Length != 8 || !bankIdentifierCodes.Contains(items[7],StringComparer.OrdinalIgnoreCase))
errorMessage.Add("Bank Identifier Code", "Invalid bank identifier code");
if (!string.IsNullOrEmpty(items[8]) && items[8].Length > 35)
errorMessage.Add(
Solution
In the object initializer syntax you're using here:
The parentheses are redundant; object initializer calls the parameterless constructor by default, so you don't need to call it explicitly.
However there's a design smell here; using this syntax tells me your
Notice how everything could be set from the outside, even with invalid state? What if
The type is crying to be immutable. Make the properties get-only, and assign their values from the constructor. If the logic for
Don't expose a
How about a tiny little class?
Then instead of exposing a
Random thoughts:
Spot the issue:
Looks like a copy-pasta bug. I think the part after the
This is clearly a bug:
...but that's sub-optimal, too; if that's meant to say "if there's no
return new StatementLineResult()
{
ErrorMessages = errorMessage,
Data = BankLineData(delimitedRecord),
IsValid = errorMessage.Count==0
};The parentheses are redundant; object initializer calls the parameterless constructor by default, so you don't need to call it explicitly.
However there's a design smell here; using this syntax tells me your
StatementLineResult class looks something like this:public class StatementLineResult
{
public Dictionary ErrorMessages { get; set; }
public SomeType Data { get; set; }
public bool IsValid { get; set; }
}Notice how everything could be set from the outside, even with invalid state? What if
IsValid is set to false when there are items in ErrorMessages? Nonsensical isn't it!The type is crying to be immutable. Make the properties get-only, and assign their values from the constructor. If the logic for
IsValid is "has no error messages", then it's probably redundant. In any case, it should be get-only and return ErrorMessages.Count == 0, not an arbitrary bool value.Don't expose a
Dictionary (or a List, for that matter) like that. Expose an IEnumerable - your client code needs only to iterate the values, and doesn't care about the Key and Value concepts. What's the type of T then?How about a tiny little class?
public class FieldValidationError
{
public FieldValidationError(string fieldName, string message)
{
_fieldName = fieldName;
_message = message;
}
private readonly string _fieldName;
public string FieldName { get { return _fieldName; } }
private readonly string _message;
public string Message { get { return _message; } }
}Then instead of exposing a
Dictionary, you can expose a much more expressive IEnumerable, and not worry about the possibility that client code could tamper with the results.Random thoughts:
- Don't compare strings to
nullor""- usestring.IsNullOrEmptyinstead... which you are using.. just not consistently.
- Don't throw
System.Exception. Use the most appropriate existing exception type, or make one if none fits the bill appropriately. But don't throw the base exception type; it forces client code tocatch (Exception), which makes it catch all kinds of nasties that you would probably rather see bubble up the call stack and crash your app. In this case, throwing anArgumentExceptionwould probably be fine; deriving anInvalidFieldCountExceptionfrom it could work too.
Spot the issue:
if (!int.TryParse(items[1], out sortCode) || items[1].Length > 6)
errorMessage.Add("Sort Code", "Invalid sort code");
if (string.IsNullOrEmpty(items[2]) || items[1].Length > 34)
errorMessage.Add("Account Number", "Invalid account number");
if (string.IsNullOrEmpty(items[3]) || items[1].Length > 35)
errorMessage.Add("Account Alias", "Invalid account alias");
if (string.IsNullOrEmpty(items[4]) || items[1].Length > 35)
errorMessage.Add("Account Name", "Invalid account name");
if(string.IsNullOrEmpty(items[5]) || items[5].Length != 3)
errorMessage.Add("Account Currency", "Invalid account currency");Looks like a copy-pasta bug. I think the part after the
|| operator is actually intended to use the same index as the part before.This is clearly a bug:
if(_transactionTypes.First(item=>item.TransactionType==items[16]) == null)First does not return null when there's no item: it throws an InvalidOperationException instead. You probably meant to use FirstOrDefault:if(_transactionTypes.FirstOrDefault(item => item.TransactionType == items[16]) == null)...but that's sub-optimal, too; if that's meant to say "if there's no
_transactionTypes item for items[16]", then it can be expressed more concisely like this:if(!_transactionTypes.Any(item => item.TransactionType == items[16]))Code Snippets
return new StatementLineResult()
{
ErrorMessages = errorMessage,
Data = BankLineData(delimitedRecord),
IsValid = errorMessage.Count==0
};public class StatementLineResult
{
public Dictionary<string,string> ErrorMessages { get; set; }
public SomeType Data { get; set; }
public bool IsValid { get; set; }
}public class FieldValidationError
{
public FieldValidationError(string fieldName, string message)
{
_fieldName = fieldName;
_message = message;
}
private readonly string _fieldName;
public string FieldName { get { return _fieldName; } }
private readonly string _message;
public string Message { get { return _message; } }
}if (!int.TryParse(items[1], out sortCode) || items[1].Length > 6)
errorMessage.Add("Sort Code", "Invalid sort code");
if (string.IsNullOrEmpty(items[2]) || items[1].Length > 34)
errorMessage.Add("Account Number", "Invalid account number");
if (string.IsNullOrEmpty(items[3]) || items[1].Length > 35)
errorMessage.Add("Account Alias", "Invalid account alias");
if (string.IsNullOrEmpty(items[4]) || items[1].Length > 35)
errorMessage.Add("Account Name", "Invalid account name");
if(string.IsNullOrEmpty(items[5]) || items[5].Length != 3)
errorMessage.Add("Account Currency", "Invalid account currency");if(_transactionTypes.First(item=>item.TransactionType==items[16]) == null)Context
StackExchange Code Review Q#104823, answer score: 6
Revisions (0)
No revisions yet.