From a055be2185447ca7f03be2c18ac8433c0b3b2167 Mon Sep 17 00:00:00 2001
From: Ronny Gunawan <3048897+ronnygunawan@users.noreply.github.com>
Date: Sat, 18 Nov 2023 22:01:08 +0700
Subject: [PATCH] Throw correct exception for incorrect number of columns
---
CsvSerializer/CsvSerializer.csproj | 2 +-
.../Internals/NativeImplGenerator.cs | 31 +++++++++++--------
CsvSerializer/SourceGenerator.cs | 4 ++-
Tests/SerializerTests.cs | 24 ++++++++++++--
4 files changed, 44 insertions(+), 17 deletions(-)
diff --git a/CsvSerializer/CsvSerializer.csproj b/CsvSerializer/CsvSerializer.csproj
index a9965b1..1077686 100644
--- a/CsvSerializer/CsvSerializer.csproj
+++ b/CsvSerializer/CsvSerializer.csproj
@@ -15,7 +15,7 @@
https://github.com/ronnygunawan/csv-serializer
csv serializer deserializer parser core
Fast CSV to object serializer and deserializer.
- 2.0.3
+ 2.0.4
true
true
diff --git a/CsvSerializer/Internals/NativeImplGenerator.cs b/CsvSerializer/Internals/NativeImplGenerator.cs
index 7d1b873..10767a4 100644
--- a/CsvSerializer/Internals/NativeImplGenerator.cs
+++ b/CsvSerializer/Internals/NativeImplGenerator.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -92,7 +91,7 @@ private enum ParserState {
InTrailingWhiteSpace
}
- public static void ReadNextLine(ref ReadOnlyMemory csv, ref Span> columns, char separator = ',') {
+ public static int ReadNextLine(ref ReadOnlyMemory csv, ref Span> columns, char separator = ',') {
ReadOnlySpan span = csv.Span;
int startOfLiteral = 0;
int endOfLiteral = 0;
@@ -106,13 +105,13 @@ public static void ReadNextLine(ref ReadOnlyMemory csv, ref Span csv, ref Span Deserialize(IFormatProvider? provider, char delimiter, bool
Span> columns = new ReadOnlyMemory[{{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()}}
diff --git a/CsvSerializer/SourceGenerator.cs b/CsvSerializer/SourceGenerator.cs
index aeb51e2..3a1f35e 100644
--- a/CsvSerializer/SourceGenerator.cs
+++ b/CsvSerializer/SourceGenerator.cs
@@ -1,4 +1,6 @@
-using System.Diagnostics;
+#if DEBUG
+using System.Diagnostics;
+#endif
using Csv.Internals;
using Microsoft.CodeAnalysis;
diff --git a/Tests/SerializerTests.cs b/Tests/SerializerTests.cs
index 5f49d45..02d2536 100644
--- a/Tests/SerializerTests.cs
+++ b/Tests/SerializerTests.cs
@@ -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");
@@ -233,6 +233,26 @@ string Serialize(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(csv, hasHeaders: true, provider: CultureInfo.InvariantCulture))
+ .Should().Throw("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(csv, hasHeaders: true, provider: CultureInfo.InvariantCulture))
+ .Should().Throw("Row must consists of 16 columns.");
+ }
}
public class ExcelModel {