Skip to content

Commit

Permalink
Merge pull request #18 from ronnygunawan/fix-no-of-columns-exc
Browse files Browse the repository at this point in the history
Throw correct exception for incorrect number of columns
  • Loading branch information
ronnygunawan authored Nov 18, 2023
2 parents 02bc1ad + a055be2 commit af25355
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 17 deletions.
2 changes: 1 addition & 1 deletion CsvSerializer/CsvSerializer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<RepositoryUrl>https://github.com/ronnygunawan/csv-serializer</RepositoryUrl>
<PackageTags>csv serializer deserializer parser core</PackageTags>
<Description>Fast CSV to object serializer and deserializer.</Description>
<Version>2.0.3</Version>
<Version>2.0.4</Version>
<PackageReleaseNotes></PackageReleaseNotes>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
Expand Down
31 changes: 18 additions & 13 deletions CsvSerializer/Internals/NativeImplGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -92,7 +91,7 @@ private enum ParserState {
InTrailingWhiteSpace
}
public static void ReadNextLine(ref ReadOnlyMemory<char> csv, ref Span<ReadOnlyMemory<char>> columns, char separator = ',') {
public static int ReadNextLine(ref ReadOnlyMemory<char> csv, ref Span<ReadOnlyMemory<char>> columns, char separator = ',') {
ReadOnlySpan<char> span = csv.Span;
int startOfLiteral = 0;
int endOfLiteral = 0;
Expand All @@ -106,13 +105,13 @@ public static void ReadNextLine(ref ReadOnlyMemory<char> csv, ref Span<ReadOnlyM
case ParserState.InEscapeSequence:
columns[col] = csv[startOfLiteral..i];
csv = csv.Slice(csv.Length - 1, 0);
return;
return col + 1;
case ParserState.InQuotedValue:
throw new CsvFormatException(csv.ToString(), "End of file in quoted literal.");
case ParserState.InTrailingWhiteSpace:
columns[col] = csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1);
csv = csv.Slice(csv.Length - 1, 0);
return;
return col + 1;
}
} else {
switch (span[i]) {
Expand Down Expand Up @@ -161,11 +160,11 @@ public static void ReadNextLine(ref ReadOnlyMemory<char> csv, ref Span<ReadOnlyM
case ParserState.InEscapeSequence:
columns[col] = csv[startOfLiteral..i];
csv = csv[(i + 1)..];
return;
return col + 1;
case ParserState.InTrailingWhiteSpace:
columns[col] = csv.Slice(startOfLiteral, endOfLiteral - startOfLiteral + 1);
csv = csv[(i + 1)..];
return;
return col + 1;
}
break;
case char c:
Expand Down Expand Up @@ -1419,18 +1418,24 @@ public List<object> Deserialize(IFormatProvider? provider, char delimiter, bool
Span<ReadOnlyMemory<char>> columns = new ReadOnlyMemory<char>[{{propertySymbols.Count}}];
bool firstRow = true;
while (csv.Length > 0) {
NativeStringSplitter.ReadNextLine(ref csv, ref columns, delimiter);
try {
int columnsRead = NativeStringSplitter.ReadNextLine(ref csv, ref columns, delimiter);
if (columnsRead != {{propertySymbols.Count}}) {
int endOfLine = csv.Span.IndexOf('\n');
string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString();
throw new CsvFormatException(typeof({{fullTypeName}}), line, "Row must consists of {{propertySymbols.Count}} columns.");
}
} catch (IndexOutOfRangeException) {
int endOfLine = csv.Span.IndexOf('\n');
string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString();
throw new CsvFormatException(typeof({{fullTypeName}}), line, "Row must consists of {{propertySymbols.Count}} columns.");
}
if (firstRow) {
firstRow = false;
if (skipHeader) {
continue;
}
}
if (columns.Length != {{propertySymbols.Count}}) {
int endOfLine = csv.Span.IndexOf('\n');
string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString();
throw new CsvFormatException(typeof({{fullTypeName}}), line, "Row must consists of {{propertySymbols.Count}} columns.");
}
{{fullTypeName}} item = Activator.CreateInstance<{{fullTypeName}}>();
if (provider is null) {
{{deserializeWithoutProviderBuilder.ToString().Trim()}}
Expand Down
4 changes: 3 additions & 1 deletion CsvSerializer/SourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Diagnostics;
#if DEBUG
using System.Diagnostics;
#endif
using Csv.Internals;
using Microsoft.CodeAnalysis;

Expand Down
24 changes: 22 additions & 2 deletions Tests/SerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ public void CsvCanBeDeserializedToPrivateType() {
[Fact]
public void HeaderNameCanBeRenamedUsingAttribute() {
ModelWithColumnNames[] items = {
new ModelWithColumnNames { ItemName = "Coconut", WeightInGrams = 900 },
new ModelWithColumnNames { ItemName = "Jackfruit", WeightInGrams = 1200 }
new() { ItemName = "Coconut", WeightInGrams = 900 },
new() { ItemName = "Jackfruit", WeightInGrams = 1200 }
};
string csv = CsvSerializer.Serialize(items, withHeaders: true);
string[] lines = csv.Split("\r\n");
Expand Down Expand Up @@ -233,6 +233,26 @@ string Serialize<T>(T item) {
True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","8/23/2019 12:00:00 AM","http://localhost:5000/",OK
""");
}

[Fact]
public void CannotDeserializeRowWithExtraColumns() {
string csv = """
"Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime","Uri","StatusCode"
True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","08/23/2019 00:00:00","http://localhost:5000/",OK,foo,bar
""";
new Action(() => CsvSerializer.Deserialize<Model>(csv, hasHeaders: true, provider: CultureInfo.InvariantCulture))
.Should().Throw<CsvFormatException>("Row must consists of 16 columns.");
}

[Fact]
public void CannotDeserializeRowWithMissingColumns() {
string csv = """
"Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime","Uri","StatusCode"
True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","08/23/2019 00:00:00","http://localhost:5000/"
""";
new Action(() => CsvSerializer.Deserialize<Model>(csv, hasHeaders: true, provider: CultureInfo.InvariantCulture))
.Should().Throw<CsvFormatException>("Row must consists of 16 columns.");
}
}

public class ExcelModel {
Expand Down

0 comments on commit af25355

Please sign in to comment.