diff --git a/CsvSerializer/CsvSerializer.csproj b/CsvSerializer/CsvSerializer.csproj index 1dad737..d49fabc 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. - 1.0.7 + 1.0.8 diff --git a/CsvSerializer/Internal/NaiveImpl/NaiveDeserializer.cs b/CsvSerializer/Internal/NaiveImpl/NaiveDeserializer.cs index 0c946ea..b99d523 100644 --- a/CsvSerializer/Internal/NaiveImpl/NaiveDeserializer.cs +++ b/CsvSerializer/Internal/NaiveImpl/NaiveDeserializer.cs @@ -182,84 +182,108 @@ public List Deserialize(IFormatProvider provider, char delimiter, bool s for (int i = 0; i < _properties.Length; i++) { switch (_deserializeAs[i]) { case DeserializeAs.SByte: - if (sbyte.TryParse(columns[i].Span, out sbyte vSByte)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && sbyte.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out sbyte vSByte)) { + _properties[i].SetValue(item, vSByte); + } else if (sbyte.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vSByte)) { _properties[i].SetValue(item, vSByte); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct sbyte format."); } break; case DeserializeAs.Byte: - if (byte.TryParse(columns[i].Span, out byte vByte)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && byte.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out byte vByte)) { + _properties[i].SetValue(item, vByte); + } else if (byte.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vByte)) { _properties[i].SetValue(item, vByte); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct byte format."); } break; case DeserializeAs.Int16: - if (short.TryParse(columns[i].Span, out short vInt16)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && short.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out short vInt16)) { + _properties[i].SetValue(item, vInt16); + } else if (short.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt16)) { _properties[i].SetValue(item, vInt16); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int16 format."); } break; case DeserializeAs.UInt16: - if (ushort.TryParse(columns[i].Span, out ushort vUInt16)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && ushort.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out ushort vUInt16)) { + _properties[i].SetValue(item, vUInt16); + } else if (ushort.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt16)) { _properties[i].SetValue(item, vUInt16); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt16 format."); } break; case DeserializeAs.Int32: - if (int.TryParse(columns[i].Span, out int vInt32)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && int.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out int vInt32)) { + _properties[i].SetValue(item, vInt32); + } else if (int.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt32)) { _properties[i].SetValue(item, vInt32); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int32 format."); } break; case DeserializeAs.UInt32: - if (uint.TryParse(columns[i].Span, out uint vUInt32)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && uint.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out uint vUInt32)) { + _properties[i].SetValue(item, vUInt32); + } else if (uint.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt32)) { _properties[i].SetValue(item, vUInt32); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt32 format."); } break; case DeserializeAs.Int64: - if (long.TryParse(columns[i].Span, out long vInt64)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && long.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out long vInt64)) { + _properties[i].SetValue(item, vInt64); + } else if (long.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vInt64)) { _properties[i].SetValue(item, vInt64); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Int64 format."); } break; case DeserializeAs.UInt64: - if (ulong.TryParse(columns[i].Span, out ulong vUInt64)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && ulong.TryParse(columns[i].Span[1..^1], NumberStyles.Integer, provider, out ulong vUInt64)) { + _properties[i].SetValue(item, vUInt64); + } else if (ulong.TryParse(columns[i].Span, NumberStyles.Integer, provider, out vUInt64)) { _properties[i].SetValue(item, vUInt64); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct UInt64 format."); } break; case DeserializeAs.Single: - if (float.TryParse(columns[i].Span, out float vSingle)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && float.TryParse(columns[i].Span[1..^1], NumberStyles.Float, provider, out float vSingle)) { + _properties[i].SetValue(item, vSingle); + } else if (float.TryParse(columns[i].Span, NumberStyles.Float, provider, out vSingle)) { _properties[i].SetValue(item, vSingle); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct floating point format."); } break; case DeserializeAs.Double: - if (double.TryParse(columns[i].Span, out double vDouble)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && double.TryParse(columns[i].Span[1..^1], NumberStyles.Float, provider, out double vDouble)) { + _properties[i].SetValue(item, vDouble); + } else if (double.TryParse(columns[i].Span, NumberStyles.Float, provider, out vDouble)) { _properties[i].SetValue(item, vDouble); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct floating point format."); } break; case DeserializeAs.Decimal: - if (decimal.TryParse(columns[i].Span, out decimal vDecimal)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && decimal.TryParse(columns[i].Span[1..^1], NumberStyles.Number, provider, out decimal vDecimal)) { + _properties[i].SetValue(item, vDecimal); + } else if (decimal.TryParse(columns[i].Span, NumberStyles.Number, provider, out vDecimal)) { _properties[i].SetValue(item, vDecimal); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct decimal format."); } break; case DeserializeAs.Boolean: - if (bool.TryParse(columns[i].Span, out bool vBoolean)) { + if (columns[i].Length >= 2 && columns[i].Span[0] == '"' && bool.TryParse(columns[i].Span[1..^1], out bool vBoolean)) { + _properties[i].SetValue(item, vBoolean); + } else if (bool.TryParse(columns[i].Span, out vBoolean)) { _properties[i].SetValue(item, vBoolean); } else if (!_isNullable[i] || columns[i].Length > 0) { throw new CsvFormatException(typeof(T), _properties[i].Name, columns[i].ToString(), "Input string was not in correct Boolean format."); diff --git a/CsvSerializer/Internal/NaiveImpl/NaiveSerializer.cs b/CsvSerializer/Internal/NaiveImpl/NaiveSerializer.cs index 2f4ed50..d9d1ca2 100644 --- a/CsvSerializer/Internal/NaiveImpl/NaiveSerializer.cs +++ b/CsvSerializer/Internal/NaiveImpl/NaiveSerializer.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Reflection; using System.Text; @@ -93,7 +94,12 @@ public void SerializeItem(IFormatProvider provider, char delimiter, StringBuilde } switch (_serializeAs[i]) { case SerializeAs.Number: - stringBuilder.AppendFormat("{0}", _properties[i].GetValue(item)); + string? str = Convert.ToString(_properties[i].GetValue(item), provider); + if (str is string && str.Contains(delimiter)) { + stringBuilder.AppendFormat("\"{0}\"", str); + } else { + stringBuilder.AppendFormat("{0}", str); + } break; case SerializeAs.String: if (((string?)_properties[i].GetValue(item))?.Replace("\"", "\"\"") is string stringValue) { @@ -106,9 +112,9 @@ public void SerializeItem(IFormatProvider provider, char delimiter, StringBuilde if (((DateTime?)_properties[i].GetValue(item)) is DateTime dateTimeValue) { stringBuilder.Append('"'); if (_columnAttributes[i]?.DateFormat is string dateFormat) { - stringBuilder.Append(dateTimeValue.ToString(dateFormat)); + stringBuilder.Append(dateTimeValue.ToString(dateFormat, provider)); } else { - stringBuilder.Append(dateTimeValue.ToString()); + stringBuilder.Append(dateTimeValue.ToString(provider)); } stringBuilder.Append('"'); } diff --git a/Tests/DynamicSerializerTests.cs b/Tests/DynamicSerializerTests.cs index 6efa261..48a068b 100644 --- a/Tests/DynamicSerializerTests.cs +++ b/Tests/DynamicSerializerTests.cs @@ -46,7 +46,7 @@ public void NullValuesAreSerializedToEmptyColumn() { DateTime = new DateTime(2019, 8, 23) }; csv = CsvSerializer.Serialize(new[] { obj }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",8/23/2019 12:00:00 AM"); + csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\"\r\nTrue,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,\"CSV Serializer\",\"8/23/2019 12:00:00 AM\""); } [Fact] diff --git a/Tests/InternationalizationTests.cs b/Tests/InternationalizationTests.cs index d66a028..5c86a1b 100644 --- a/Tests/InternationalizationTests.cs +++ b/Tests/InternationalizationTests.cs @@ -1,11 +1,17 @@ using Csv; using FluentAssertions; using System; +using System.Collections.Generic; using System.Globalization; using Xunit; namespace Tests { public class InternationalizationTests { + private class MapCoordinates { + public double Latitude { get; set; } + public double Longitude { get; set; } + } + [Fact] public void CanSerializeAndDeserializeInEN_USLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("en-US"); @@ -36,6 +42,26 @@ public void CanSerializeAndDeserializeInEN_GBLocale() { model.Double.Should().Be(123_456.789); } + [Fact] + public void CanSerializeAndDeserializeWithModifiedThreadCulture() { + CultureInfo temp = CultureInfo.CurrentCulture; + try { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("id-ID"); + DecimalAndDouble model = new DecimalAndDouble { + Decimal = 123_456.789m, + Double = 123_456.789 + }; + string csv = CsvSerializer.Serialize(new[] { model }); + DecimalAndDouble[] deserialized = CsvSerializer.Deserialize(csv); + deserialized.Length.Should().Be(1); + model = deserialized[0]; + model.Decimal.Should().Be(123_456.789m); + model.Double.Should().Be(123_456.789); + } finally { + CultureInfo.CurrentCulture = temp; + } + } + [Fact] public void CanSerializeAndDeserializeInID_IDLocale() { IFormatProvider provider = CultureInfo.GetCultureInfo("id-ID"); diff --git a/Tests/SerializerTests.cs b/Tests/SerializerTests.cs index 5a3d60b..0296fb3 100644 --- a/Tests/SerializerTests.cs +++ b/Tests/SerializerTests.cs @@ -1,6 +1,7 @@ using Csv; using FluentAssertions; using System; +using System.Globalization; using System.Linq; using System.Net; using Xunit; @@ -52,7 +53,7 @@ public void PublicTypesAreSerializedUsingDynamicSerializer() { StatusCode = HttpStatusCode.OK }; string csv = CsvSerializer.Serialize(new[] { item }, withHeaders: true); - csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\",\"Uri\",\"StatusCode\"\r\nTrue,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"); + csv.Should().Be("\"Bool\",\"Byte\",\"SByte\",\"Short\",\"UShort\",\"Int\",\"UInt\",\"Long\",\"ULong\",\"Float\",\"Double\",\"Decimal\",\"String\",\"DateTime\",\"Uri\",\"StatusCode\"\r\nTrue,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] @@ -182,7 +183,7 @@ public void CanDeserializeCsvFromExcel() { string csv = @"No;Name;Price;Weight;CreatedDate;IsSuspended 10;Deflector, Dust (For Rear Differential);200000;20,5;13/12/2019;FALSE 13;""Deflector; Tire; Filter"";150000;15,5;20/11/2019;TRUE"; - ExcelModel[] models = CsvSerializer.Deserialize(csv, hasHeaders: true, delimiter: ';'); + ExcelModel[] models = CsvSerializer.Deserialize(csv, hasHeaders: true, delimiter: ';', provider: CultureInfo.GetCultureInfo("id-ID")); models.Count().Should().Be(2); } }