// Copyright 2009-2022 Josh Close // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using CsvHelper.Configuration; using CsvHelper.TypeConversion; using Int32Converter = CsvHelper.TypeConversion.Int32Converter; using System.Dynamic; using Xunit; using System.Threading; namespace CsvHelper.Tests { public class CsvWriterTests { public CsvWriterTests() { Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); } [Fact] public void WriteFieldTest() { var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); var date = DateTime.Now.ToString(); var guid = Guid.NewGuid().ToString(); csv.WriteField("one"); csv.WriteField("one, two"); csv.WriteField("one \"two\" three"); csv.WriteField(" one "); csv.WriteField(date); csv.WriteField((short)1); csv.WriteField(2); csv.WriteField((long)3); csv.WriteField((float)4); csv.WriteField((double)5); csv.WriteField(guid); csv.NextRecord(); var reader = new StreamReader(stream); stream.Position = 0; var data = reader.ReadToEnd(); Assert.Equal("one,\"one, two\",\"one \"\"two\"\" three\",\" one \"," + date + ",1,2,3,4,5," + guid + "\r\n", data); } [Fact] public void WriteRecordTest() { var record = new TestRecord { IntColumn = 1, StringColumn = "string column", IgnoredColumn = "ignored column", FirstColumn = "first column", }; var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); csv.Context.RegisterClassMap(); csv.WriteRecord(record); csv.NextRecord(); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); var expected = "first column,1,string column,test\r\n"; Assert.Equal(expected, csvFile); } [Fact] public void WriteRecordNoIndexesTest() { var record = new TestRecordNoIndexes { IntColumn = 1, StringColumn = "string column", IgnoredColumn = "ignored column", FirstColumn = "first column", }; using (var stream = new MemoryStream()) using (var writer = new StreamWriter(stream) { AutoFlush = true }) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.Context.RegisterClassMap(); csv.WriteRecord(record); csv.NextRecord(); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); var expected = "1,string column,first column,test\r\n"; Assert.Equal(expected, csvFile); } } [Fact] public void WriteRecordsTest() { var records = new List { new TestRecord { IntColumn = 1, StringColumn = "string column", IgnoredColumn = "ignored column", FirstColumn = "first column", }, new TestRecord { IntColumn = 2, StringColumn = "string column 2", IgnoredColumn = "ignored column 2", FirstColumn = "first column 2", }, }; var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); csv.Context.RegisterClassMap(); csv.WriteRecords(records); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); var expected = "FirstColumn,Int Column,StringColumn,TypeConvertedColumn\r\n"; expected += "first column,1,string column,test\r\n"; expected += "first column 2,2,string column 2,test\r\n"; Assert.Equal(expected, csvFile); } [Fact] public void WriteEmptyArrayTest() { var records = new TestRecord[] { }; var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); csv.Context.RegisterClassMap(); csv.WriteRecords(records); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); var expected = "FirstColumn,Int Column,StringColumn,TypeConvertedColumn\r\n"; Assert.Equal(expected, csvFile); } [Fact] public void WriteRecordsCalledWithTwoParametersTest() { var records = new List { new TestRecord { IntColumn = 1, StringColumn = "string column", IgnoredColumn = "ignored column", FirstColumn = "first column", }, new TestRecord { IntColumn = 2, StringColumn = "string column 2", IgnoredColumn = "ignored column 2", FirstColumn = "first column 2", }, }; var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); csv.Context.RegisterClassMap(); csv.WriteRecords(records); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); var expected = "FirstColumn,Int Column,StringColumn,TypeConvertedColumn\r\n"; expected += "first column,1,string column,test\r\n"; expected += "first column 2,2,string column 2,test\r\n"; Assert.Equal(expected, csvFile); } [Fact] public void WriteRecordNoHeaderTest() { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { HasHeaderRecord = false, }; var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, config); csv.Context.RegisterClassMap(); csv.WriteRecord(new TestRecord()); csv.NextRecord(); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); Assert.Equal(",0,,test\r\n", csvFile); } [Fact] public void WriteRecordWithNullRecordTest() { var record = new TestRecord { IntColumn = 1, StringColumn = "string column", IgnoredColumn = "ignored column", FirstColumn = "first column", }; var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); csv.Context.RegisterClassMap(); csv.WriteRecord(record); csv.NextRecord(); csv.WriteRecord((TestRecord)null); csv.NextRecord(); csv.WriteRecord(record); csv.NextRecord(); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); var expected = new TestStringBuilder(csv.Configuration.NewLine); expected.AppendLine("first column,1,string column,test"); expected.AppendLine(",,,"); expected.AppendLine("first column,1,string column,test"); Assert.Equal(expected, csvFile); } [Fact] public void WriteRecordWithReferencesTest() { var record = new Person { FirstName = "First Name", LastName = "Last Name", HomeAddress = new Address { Street = "Home Street", City = "Home City", State = "Home State", Zip = "Home Zip", }, WorkAddress = new Address { Street = "Work Street", City = "Work City", State = "Work State", Zip = "Work Zip", } }; var stream = new MemoryStream(); var writer = new StreamWriter(stream) { AutoFlush = true }; var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); csv.Context.RegisterClassMap(); csv.WriteRecord(record); csv.NextRecord(); stream.Position = 0; var reader = new StreamReader(stream); var csvFile = reader.ReadToEnd(); var expected = "First Name,Last Name,Home Street,Home City,Home State,Home Zip,Work Street,Work City,Work State,Work Zip\r\n"; Assert.Equal(expected, csvFile); } [Fact] public void WriteRecordsAllFieldsQuotedTest() { var record = new TestRecord { IntColumn = 1, StringColumn = "string column", IgnoredColumn = "ignored column", FirstColumn = "first column", }; var config = new CsvConfiguration(CultureInfo.InvariantCulture) { ShouldQuote = _ => true, }; string csv; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csvWriter = new CsvWriter(writer, config)) { csvWriter.Context.RegisterClassMap(); csvWriter.WriteRecord(record); csvWriter.NextRecord(); writer.Flush(); stream.Position = 0; csv = reader.ReadToEnd(); } var expected = "\"first column\",\"1\",\"string column\",\"test\"\r\n"; Assert.Equal(expected, csv); } [Fact] public void WriteRecordsNoFieldsQuotedTest() { var record = new TestRecord { IntColumn = 1, StringColumn = "string \" column", IgnoredColumn = "ignored column", FirstColumn = "first, column", }; var config = new CsvConfiguration(CultureInfo.InvariantCulture) { ShouldQuote = _ => false, }; string csv; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csvWriter = new CsvWriter(writer, config)) { csvWriter.Context.RegisterClassMap(); csvWriter.WriteRecord(record); csvWriter.NextRecord(); writer.Flush(); stream.Position = 0; csv = reader.ReadToEnd(); } var expected = "first, column,1,string \" column,test\r\n"; Assert.Equal(expected, csv); } [Fact] public void WriteHeaderTest() { string csv; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csvWriter = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csvWriter.Context.RegisterClassMap(); csvWriter.WriteHeader(typeof(TestRecord)); csvWriter.NextRecord(); writer.Flush(); stream.Position = 0; csv = reader.ReadToEnd(); } const string Expected = "FirstColumn,Int Column,StringColumn,TypeConvertedColumn\r\n"; Assert.Equal(Expected, csv); } [Fact] public void WriteRecordWithDelimiterInFieldTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var record = new TestSinglePropertyRecord { Name = "one,two" }; csv.WriteRecord(record); csv.NextRecord(); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("\"one,two\"\r\n", text); } } [Fact] public void WriteRecordWithQuoteInFieldTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var record = new TestSinglePropertyRecord { Name = "one\"two" }; csv.WriteRecord(record); csv.NextRecord(); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("\"one\"\"two\"\r\n", text); } } [Fact] public void WriteRecordWithQuoteAllFieldsOnAndDelimiterInFieldTest() { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { ShouldQuote = _ => true, }; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { var record = new TestSinglePropertyRecord { Name = "one,two" }; csv.WriteRecord(record); csv.NextRecord(); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("\"one,two\"\r\n", text); } } [Fact] public void WriteRecordWithQuoteAllFieldsOnAndQuoteInFieldTest() { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { ShouldQuote = _ => true, }; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { var record = new TestSinglePropertyRecord { Name = "one\"two" }; csv.WriteRecord(record); csv.NextRecord(); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("\"one\"\"two\"\r\n", text); } } [Fact] public void WriteRecordsWithInvariantCultureTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.Context.RegisterClassMap(); var record = new TestRecord { IntColumn = 1, FirstColumn = "first column", IgnoredColumn = "ignored column", StringColumn = "string column", }; csv.WriteRecord(record); writer.Flush(); stream.Position = 0; var csvString = reader.ReadToEnd(); } } [Fact] public void WriteNoGetterTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var list = new List { new TestPrivateGet { Id = 1, Name = "one" } }; csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var data = reader.ReadToEnd(); var expected = new TestStringBuilder(csv.Configuration.NewLine); expected.AppendLine("Id"); expected.AppendLine("1"); Assert.Equal(expected.ToString(), data); } } [Fact] public void WriteDynamicTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.WriteRecord(new { Id = 1, Name = "one" }); csv.NextRecord(); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("1,one\r\n", text); } } [Fact] public void WritePrimitivesRecordsHasHeaderTrueTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var list = new List { 1, 2, 3 }; csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("1\r\n2\r\n3\r\n", text); } } [Fact] public void WritePrimitivesRecordsHasHeaderFalseTest() { var config = new CsvConfiguration(CultureInfo.InvariantCulture) { HasHeaderRecord = false, }; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { var list = new List { 1, 2, 3 }; csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("1\r\n2\r\n3\r\n", text); } } [Fact] public void WriteStructRecordsTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var list = new List { new TestStruct { Id = 1, Name = "one" }, new TestStruct { Id = 2, Name = "two" }, }; csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("Id,Name\r\n1,one\r\n2,two\r\n", text); } } [Fact] public void WriteStructReferenceRecordsTest() { var list = new List { new TestStructParent { Test = new TestStruct { Id = 1, Name = "one", }, }, }; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.Context.RegisterClassMap(); csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var data = reader.ReadToEnd(); Assert.Equal("Id,Name\r\n1,one\r\n", data); } } [Fact] public void WriteNestedHeadersTest() { var list = new List { new Person { FirstName = "first", LastName = "last", HomeAddress = new Address { City = "home city", State = "home state", Street = "home street", Zip = "home zip", }, WorkAddress = new Address { City = "work city", State = "work state", Street = "work street", Zip = "work zip", }, }, }; var config = new CsvConfiguration(CultureInfo.InvariantCulture) { ReferenceHeaderPrefix = args => $"{args.MemberName}." }; using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, config)) { csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); var expected = new TestStringBuilder(csv.Configuration.NewLine); expected.AppendLine("FirstName,LastName,HomeAddress.Street,HomeAddress.City,HomeAddress.State,HomeAddress.Zip,WorkAddress.Street,WorkAddress.City,WorkAddress.State,WorkAddress.Zip"); expected.AppendLine("first,last,home street,home city,home state,home zip,work street,work city,work state,work zip"); Assert.Equal(expected.ToString(), text); } } [Fact] public void WriteEmptyListTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var list = new List(); csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var data = reader.ReadToEnd(); Assert.False(string.IsNullOrWhiteSpace(data)); } } [Fact] public void ClassWithStaticAutoMappingTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { TestWithStatic.Name = "one"; var records = new List { new TestWithStatic { Id = 1 }, }; csv.WriteRecords(records); } } [Fact] public void ClassWithStaticUsingMappingTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.Context.RegisterClassMap(); TestWithStatic.Name = "one"; var records = new List { new TestWithStatic { Id = 1 }, }; csv.WriteRecords(records); } } [Fact] public void WriteDynamicListTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var list = new List(); dynamic record = new { Id = 1, Name = "one" }; list.Add(record); csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("Id,Name\r\n1,one\r\n", text); } } [Fact] public void WriteExpandoListTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { var list = new List(); dynamic record = new ExpandoObject(); record.Id = 1; record.Name = "one"; list.Add(record); csv.WriteRecords(list); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("Id,Name\r\n1,one\r\n", text); } } [Fact] public void WriteInternalConstructorClassTest() { using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) { csv.WriteRecords(new List()); writer.Flush(); stream.Position = 0; var text = reader.ReadToEnd(); Assert.Equal("One\r\n", text); } } private class GetOnly { internal GetOnly(string someParam) { } public int One { get; } } private class TestWithStatic { public int Id { get; set; } public static string Name { get; set; } } private sealed class TestWithStaticMap : ClassMap { public TestWithStaticMap() { Map(m => m.Id); } } private class TestStructParent { public TestStruct Test { get; set; } } private sealed class TestStructParentMap : ClassMap { public TestStructParentMap() { References(m => m.Test); } } private struct TestStruct { public int Id { get; set; } public string Name { get; set; } } private sealed class TestStructMap : ClassMap { public TestStructMap() { Map(m => m.Id); Map(m => m.Name); } } private class TestPrivateGet { public int Id { get; set; } public string Name { private get; set; } } private class TestSinglePropertyRecord { public string Name { get; set; } } private class TestRecord { public int IntColumn { get; set; } public string StringColumn { get; set; } public string IgnoredColumn { get; set; } public string FirstColumn { get; set; } public string TypeConvertedColumn { get; set; } } private sealed class TestRecordMap : ClassMap { public TestRecordMap() { Map(m => m.IntColumn).Name("Int Column").Index(1).TypeConverter(); Map(m => m.StringColumn); Map(m => m.FirstColumn).Index(0); Map(m => m.TypeConvertedColumn).TypeConverter(); } } private class TestRecordNoIndexes { public int IntColumn { get; set; } public string StringColumn { get; set; } public string IgnoredColumn { get; set; } public string FirstColumn { get; set; } public string TypeConvertedColumn { get; set; } } private sealed class TestRecordNoIndexesMap : ClassMap { public TestRecordNoIndexesMap() { Map(m => m.IntColumn).Name("Int Column").TypeConverter(); Map(m => m.StringColumn); Map(m => m.FirstColumn); Map(m => m.TypeConvertedColumn).TypeConverter(); } } private class TestTypeConverter : ITypeConverter { public string ConvertToString(object value, IWriterRow row, MemberMapData propertyMapData) { return "test"; } public object ConvertFromString(string text, IReaderRow row, MemberMapData propertyMapData) { throw new NotImplementedException(); } public bool CanConvertFrom(Type type) { throw new NotImplementedException(); } public bool CanConvertTo(Type type) { return true; } } private class Person { public string FirstName { get; set; } public string LastName { get; set; } public Address HomeAddress { get; set; } public Address WorkAddress { get; set; } } private class Address { public string Street { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } } private sealed class PersonMap : ClassMap { public PersonMap() { Map(m => m.FirstName); Map(m => m.LastName); References(m => m.HomeAddress); References(m => m.WorkAddress); } } private sealed class HomeAddressMap : ClassMap
{ public HomeAddressMap() { Map(m => m.Street).Name("HomeStreet"); Map(m => m.City).Name("HomeCity"); Map(m => m.State).Name("HomeState"); Map(m => m.Zip).Name("HomeZip"); } } private sealed class WorkAddressMap : ClassMap
{ public WorkAddressMap() { Map(m => m.Street).Name("WorkStreet"); Map(m => m.City).Name("WorkCity"); Map(m => m.State).Name("WorkState"); Map(m => m.Zip).Name("WorkZip"); } } } }