Skip to content

Commit

Permalink
fix: improve coherency and robusteness of CsvDataReader (#83)
Browse files Browse the repository at this point in the history
- this[] returns the same value than GetValue
- GetFieldValue also works when no schema is defined.
- FieldType returns object when no schema is defined or field is not defined
  • Loading branch information
Seddryck authored Jan 5, 2025
1 parent 9504275 commit 86360d5
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 20 deletions.
38 changes: 38 additions & 0 deletions PocketCsvReader.Testing/CsvDataReaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,16 @@ public void GetValue_WithSchemaAndFormatDateTimeAndShort_CorrectParsing(string r
Assert.That(dataReader.GetFieldType(0), Is.EqualTo(typeof(DateTime)));
Assert.That(dataReader.GetFieldType(1), Is.EqualTo(typeof(short)));
Assert.That(dataReader.GetValue(0), Is.EqualTo(new DateTime(2025, 1, 4, 14, 35, 8)));
Assert.That(dataReader[0], Is.EqualTo(new DateTime(2025, 1, 4, 14, 35, 8)));
Assert.That(dataReader.GetDate(0), Is.EqualTo(new DateOnly(2025, 1, 4)));
Assert.That(dataReader.GetTime(0), Is.EqualTo(new TimeOnly(14, 35, 8)));
Assert.That(dataReader.GetValue(1), Is.EqualTo((short)108));
Assert.That(dataReader[1], Is.EqualTo((short)108));
Assert.That(dataReader.GetInt32(1), Is.EqualTo(108));
Assert.That(dataReader.GetInt64(1), Is.EqualTo((short)108));
Assert.That(dataReader.GetFloat(1), Is.EqualTo((float)108));
Assert.That(dataReader.GetDouble(1), Is.EqualTo((double)108));
Assert.That(dataReader.GetDecimal(1), Is.EqualTo((decimal)108));
}

[Test]
Expand Down Expand Up @@ -367,6 +376,35 @@ public void GetFieldValue_WithSchemaAndFormatDateTimeAndShort_CorrectParsing(str
Assert.That(dataReader.GetFieldValue<decimal>(1), Is.EqualTo(108m));
}

[Test]
[TestCase("foo;bar\r\n2025-01-04T14:35:08;108")]
public void GetFieldValue_WithoutSchema_CorrectParsing(string record)
{
var buffer = new MemoryStream(Encoding.UTF8.GetBytes(record));

var builder = new CsvReaderBuilder()
.WithDialect(
(dialect) => dialect
.WithDelimiter(';')
.WithHeader(true)
);
using var dataReader = builder.Build().ToDataReader(buffer);
dataReader.Read();
Assert.That(dataReader.FieldCount, Is.EqualTo(2));
Assert.That(dataReader.GetName(0), Is.EqualTo("foo"));
Assert.That(dataReader.GetName(1), Is.EqualTo("bar"));
Assert.That(dataReader.GetFieldType(0), Is.EqualTo(typeof(object)));
Assert.That(dataReader.GetFieldType(1), Is.EqualTo(typeof(object)));
Assert.That(dataReader.GetFieldValue<DateTime>(0), Is.TypeOf<DateTime>());
Assert.That(dataReader.GetFieldValue<short>(1), Is.TypeOf<short>());
Assert.That(dataReader.GetFieldValue<int>(1), Is.TypeOf<int>());
Assert.That(dataReader.GetFieldValue<decimal>(1), Is.TypeOf<decimal>());
Assert.That(dataReader.GetFieldValue<DateTime>(0), Is.EqualTo(new DateTime(2025, 1, 4, 14, 35, 8)));
Assert.That(dataReader.GetFieldValue<short>(1), Is.EqualTo((short)108));
Assert.That(dataReader.GetFieldValue<int>(1), Is.EqualTo(108));
Assert.That(dataReader.GetFieldValue<decimal>(1), Is.EqualTo(108m));
}

[Test]
[TestCase("Ansi")]
[TestCase("Utf16-BE")]
Expand Down
34 changes: 14 additions & 20 deletions PocketCsvReader/CsvDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using System.Threading.Tasks;
using System.Globalization;
using PocketCsvReader.Configuration;
using System.Reflection;
using System.Xml.Linq;

namespace PocketCsvReader;
public class CsvDataReader : IDataReader
Expand Down Expand Up @@ -141,16 +143,7 @@ private void HandleUnexpectedFields(int expectedLength)

public object this[int i]
{
get
{
if (i < Record!.FieldSpans.Length && Fields!.Length > 0)
return Record.Slice(i).ToString();
if (i < Fields!.Length)
return Profile.ParserOptimizations.HandleSpecialValues ? Profile.MissingCell : string.Empty;
if (Fields!.Length == 0)
throw new InvalidOperationException("Values are not defined yet.");
throw new IndexOutOfRangeException("Index out of range.");
}
get => GetValue(i);
}

public object this[string name]
Expand All @@ -160,11 +153,7 @@ public object this[string name]
if (Fields is null)
throw new InvalidOperationException("Fields are not defined yet.");
var index = Array.IndexOf(Fields, name);
if (index < Record!.FieldSpans.Length)
return Record.Slice(index).ToString();
if (index < Fields!.Length)
return Profile.ParserOptimizations.HandleSpecialValues ? Profile.MissingCell : string.Empty;
throw new InvalidOperationException($"Field '{name}' not found.");
return GetValue(index);
}
}

Expand All @@ -190,7 +179,7 @@ public DateTime GetDateTime(int i)
var netFormat = new DateTimeFormatConverter().Convert(field.Format);
return DateTime.ParseExact(GetValueOrThrow(i), netFormat, CultureInfo.InvariantCulture);
}

return DateTime.Parse(GetValueOrThrow(i), CultureInfo.InvariantCulture);
}

Expand Down Expand Up @@ -231,7 +220,11 @@ public DateTimeOffset GetDateTimeOffset(int i)
public double GetDouble(int i) => double.Parse(GetValueOrThrow(i), CultureInfo.InvariantCulture);
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)]
public Type GetFieldType(int i)
=> GetFieldDescriptor(i).RuntimeType;
{
if (TryGetFieldDescriptor(i, out var field))
return field.RuntimeType;
return typeof(object);
}

protected FieldDescriptor GetFieldDescriptor(int i)
{
Expand Down Expand Up @@ -309,6 +302,8 @@ public object GetValue(int i)
{
if (i >= FieldCount)
throw new IndexOutOfRangeException($"Field index '{i}' is out of range.");
if (i < Fields!.Length && i >= Record!.FieldSpans.Length)
return Profile.ParserOptimizations.HandleSpecialValues ? Profile.MissingCell : string.Empty;

if (!TryGetFieldDescriptor(i, out var field)
|| !TypeFunctions.TryGetFunction(field.RuntimeType, out var func))
Expand All @@ -322,8 +317,7 @@ public T GetFieldValue<T>(int i)
if (i >= FieldCount)
throw new IndexOutOfRangeException($"Field index '{i}' is out of range.");

if (!TryGetFieldDescriptor(i, out var field)
|| !TypeFunctions.TryGetFunction<T>(out var func))
if (!TypeFunctions.TryGetFunction<T>(out var func))
throw new NotImplementedException($"No function registered for type {typeof(T).Name}");

return func.Invoke(i);
Expand Down Expand Up @@ -382,7 +376,7 @@ public bool TryGetFunction<T>([NotNullWhen(true)] out Func<int, T>? func)
{
if (_typeToFunctionMap.TryGetValue(typeof(T), out var value))
{
func = (Func<int, T>)value;
func = (Func<int, T>)value;
return true;
}
func = null;
Expand Down

0 comments on commit 86360d5

Please sign in to comment.