Skip to content

Commit 3a1fd8d

Browse files
committed
- Add CustomConterter support.
1 parent f0cc894 commit 3a1fd8d

11 files changed

Lines changed: 239 additions & 109 deletions

File tree

README.md

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
Example: Simple pipe '|' separated Delimeter File is shown below (this could even be comma ',' separated CSV)
2121
```
22-
|Mr|Jack Marias|Male|London|Active|||
23-
|Dr|Bony Stringer|Male|New Jersey|Active||Paid|
22+
|Mr|Jack Marias|Male|London, UK|Active|||
23+
|Dr|Bony Stringer|Male|New Jersey, US|Active||Paid|
2424
|Mrs|Mary Ward|Female||Active|||
2525
|Mr|Robert Webb|||Active|||
2626
```
@@ -66,8 +66,8 @@ var parser = serviceProvider.GetService<IParser>();
6666
#### <ins>Step 2<ins>. Define the `IFileLine` implementation to parse a file record into a strongly typed line class.
6767
Consider the file below. To parse a row into a C# class, you need to implement `IFileLine` interface. By doing this you create a strongly typed line representation for each row in the file.
6868
```
69-
|Mr|Jack Marias|Male|London|
70-
|Dr|Bony Stringer|Male|New Jersey|
69+
|Mr|Jack Marias|Male|London, UK|
70+
|Dr|Bony Stringer|Male|New Jersey, US|
7171
|Mrs|Mary Ward|Female||
7272
|Mr|Robert Webb|||
7373
```
@@ -100,7 +100,7 @@ public class Employee : IFileLine
100100
public string Name { get; set; }
101101
[Column(2)]
102102
public EnumGender Sex { get; set; }
103-
[Column(3, "London")]
103+
[Column(3, "London, UK")]
104104
public string Location { get; set; }
105105
106106
// IFileLine Members
@@ -118,13 +118,16 @@ ii. By providing the list of delimiter separated string values to parse method.
118118
```
119119
var lines = new[]
120120
{
121-
"|Mr|Jack Marias|Male|London|",
122-
"|Dr|Bony Stringer|Male|New Jersey|",
121+
"|Mr|Jack Marias|Male|London, UK|",
122+
"|Dr|Bony Stringer|Male|New Jersey, US|",
123123
};
124124
125125
var records = new Parser('|').Parse<Employee>(lines);
126126
```
127127
#### <ins>Step 3<ins>. Advanced Parsing of data using nested types in the FileLine class.
128+
129+
**Case 1**: Write your own Custom Converter to parse data to a custom type.
130+
128131
You could implement advance parsing of data by implementing `TypeConverter` class. Suppose we have to change the Name string property in Employee class above to a `NameType` property shown below.
129132
```
130133
public class Employee : IFileLine
@@ -210,7 +213,52 @@ public class NameType
210213
}
211214
}
212215
```
213-
Now parsing the file should hydrate data correctly to the Employee FileLine class and its nested name type.
216+
Now parsing the file should hydrate the name correctly to the Employee FileLine class with nested name type.
217+
218+
**Case 2**: Use `CustomeConverter<T>` included with Parsely, where `T` is the custom type.
219+
220+
Please see example below on how out of the box converter can be used in defining custom `LocationType` class to include in Employee line class.
221+
```
222+
public class Employee : IFileLine
223+
{
224+
[Column(0)]
225+
public string Title { get; set; }
226+
[Column(1)]
227+
public NameType Name { get; set; }
228+
[Column(2)]
229+
public EnumGender Sex { get; set; }
230+
[Column(3, "London, UK")]
231+
public LocationType Location { get; set; }
232+
233+
// IFileLine Members
234+
public int Index { get; set; }
235+
public IList<string> Errors { get; set; }
236+
}
237+
```
238+
You need to derive the custom `LocationType` type from `ICustomType` and implement the `Parse(string)` method.
239+
You also need to decorate the custom type with ` [TypeConverter(typeof(CustomConverter<LocationType>))]` attribute.
240+
```
241+
[TypeConverter(typeof(CustomConverter<LocationType>))]
242+
public class LocationType : ICustomType
243+
{
244+
public string City { get; set; }
245+
public string Country { get; set; }
246+
247+
public static LocationType Parse(string input)
248+
{
249+
var values = input.Split(',');
250+
if (values.Length == 1)
251+
return new LocationType { City = values[0] };
252+
if (values.Length == 2)
253+
return new LocationType { City = values[0], Country = values[1] };
254+
255+
// return default - London, UK
256+
return new LocationType { City = "London", Country ="UK" };
257+
}
258+
}
259+
```
260+
Now parsing the file should hydrate the location value correctly to the Employee FileLine class with nested LocationType type.
261+
214262

215263
## Support
216264

src/Parsley/CustomConverter.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.ComponentModel;
2+
using System.Globalization;
3+
4+
namespace parsley
5+
{
6+
public class CustomConverter<T> : TypeConverter where T: ICustomType, new()
7+
{
8+
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
9+
{
10+
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
11+
}
12+
13+
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
14+
{
15+
string stringValue;
16+
object result;
17+
18+
result = null;
19+
stringValue = value as string;
20+
21+
if (!string.IsNullOrEmpty(stringValue))
22+
{
23+
result = new T().Parse(stringValue);
24+
}
25+
26+
return result ?? base.ConvertFrom(context, culture, value);
27+
}
28+
}
29+
}

src/Parsley/ICustomType.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace parsley
2+
{
3+
public interface ICustomType
4+
{
5+
ICustomType Parse(string column);
6+
}
7+
}

tests/Parsley.Tests/FileLine.cs

Lines changed: 0 additions & 89 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.ComponentModel;
2+
using parsley;
3+
4+
namespace Parsley.Tests.FileLines
5+
{
6+
[TypeConverter(typeof(CustomConverter<CodeType>))]
7+
public class CodeType : ICustomType
8+
{
9+
public string Batch { get; set; }
10+
public int SerialNo { get; set; }
11+
12+
public ICustomType Parse(string input)
13+
{
14+
if (string.IsNullOrEmpty(input))
15+
return null;
16+
17+
var codes = input.Split('-');
18+
19+
if (codes.Length != 2 || !int.TryParse(codes[1], out int serialNo))
20+
throw new FormatException($"Invalid code format: {input}");
21+
22+
return new CodeType { Batch = codes[0], SerialNo = serialNo };
23+
}
24+
}
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using parsley;
2+
3+
namespace Parsley.Tests.FileLines
4+
{
5+
public class FileLine : IFileLine
6+
{
7+
public int Index { get; set; }
8+
public IList<string> Errors { get; set; }
9+
10+
public FileLine()
11+
{
12+
Errors = [];
13+
}
14+
15+
[Column(0)]
16+
public CodeType Code { get; set; }
17+
18+
[Column(1)]
19+
public NameType Name { get; set; }
20+
21+
[Column(2)]
22+
public bool IsActive { get; set; }
23+
24+
[Column(3)]
25+
public Subcription Subcription { get; set; }
26+
}
27+
28+
public enum Subcription
29+
{
30+
None,
31+
Paid,
32+
Free
33+
}
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.ComponentModel;
2+
using System.Globalization;
3+
4+
namespace Parsley.Tests.FileLines
5+
{
6+
internal class NameConverter : TypeConverter
7+
{
8+
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
9+
{
10+
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
11+
}
12+
13+
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
14+
{
15+
string stringValue;
16+
object result;
17+
18+
result = null;
19+
stringValue = value as string;
20+
21+
if (!string.IsNullOrEmpty(stringValue))
22+
result = NameType.Parse(stringValue);
23+
24+
return result ?? base.ConvertFrom(context, culture, value);
25+
}
26+
}
27+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.ComponentModel;
2+
3+
namespace Parsley.Tests.FileLines
4+
{
5+
[TypeConverter(typeof(NameConverter))]
6+
public class NameType
7+
{
8+
public string FirstName { get; set; }
9+
public string Surname { get; set; }
10+
11+
public static NameType Parse(string input)
12+
{
13+
var values = input.Split(' ');
14+
15+
if (values.Length == 1)
16+
return new NameType { FirstName = values[0] };
17+
18+
if (values.Length == 2)
19+
return new NameType { FirstName = values[0], Surname = values[1] };
20+
21+
if (values.Length > 2)
22+
{
23+
var forenames = string.Empty;
24+
for (var i = 0; i < values.Length - 1; i++)
25+
forenames += string.Concat(values[i]) + " ";
26+
27+
return new NameType { FirstName = forenames.Trim(), Surname = values[values.Length - 1] };
28+
}
29+
30+
return new NameType { FirstName = input };
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)