Skip to content

Commit

Permalink
Added ability to specify a constant value for a mapped property. When…
Browse files Browse the repository at this point in the history
… reading, the same value gets set for every record for the property. When writing, the same value gets written for every record for the property.
  • Loading branch information
JoshClose committed Sep 26, 2016
1 parent fd2b803 commit 8638099
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 31 deletions.
2 changes: 2 additions & 0 deletions src/CsvHelper.Tests/CsvHelper.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
<Compile Include="ParserBadDataTests.cs" />
<Compile Include="Parsing\CommentTests.cs" />
<Compile Include="Parsing\DelimiterTests.cs" />
<Compile Include="Reading\ConstantTests.cs" />
<Compile Include="Reading\ReadHeaderTests.cs" />
<Compile Include="Reading\ShouldSkipRecordTests.cs" />
<Compile Include="Reading\TryGetTests.cs" />
Expand All @@ -128,6 +129,7 @@
<Compile Include="TypeConversion\TypeConverterFactoryTests.cs" />
<Compile Include="TypeConversion\TypeConverterOptionsFactoryTests.cs" />
<Compile Include="TypeConversion\TypeConverterTests.cs" />
<Compile Include="Writing\ConstantTests.cs" />
<Compile Include="Writing\DynamicTests.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/CsvHelper.Tests/CsvReaderDefaultValueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private sealed class TestMap : CsvClassMap<Test>
public TestMap()
{
Map( m => m.Id ).Default( -1 );
Map( m => m.Name ).Default( null );
Map( m => m.Name ).Default( (string)null );
Map( m => m.Order ).Default( -2 );
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/CsvHelper.Tests/CsvReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,7 @@ private sealed class TestDefaultValuesMap : CsvClassMap<TestDefaultValues>
public TestDefaultValuesMap()
{
Map( m => m.IntColumn ).Default( -1 );
Map( m => m.StringColumn ).Default( null );
Map( m => m.StringColumn ).Default( (string)null );
}
}

Expand Down
50 changes: 50 additions & 0 deletions src/CsvHelper.Tests/Reading/ConstantTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CsvHelper.Configuration;
using CsvHelper.Tests.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CsvHelper.Tests.Reading
{
[TestClass]
public class ConstantTests
{
[TestMethod]
public void ConstantAlwaysReturnsSameValueTest()
{
var rows = new Queue<string[]>();
rows.Enqueue( new[] { "Id", "Name" } );
rows.Enqueue( new[] { "1", "one" } );
rows.Enqueue( new[] { "2", "two" } );
rows.Enqueue( null );
var parser = new ParserMock( rows );

var csv = new CsvReader( parser );
csv.Configuration.RegisterClassMap<TestMap>();
var records = csv.GetRecords<Test>().ToList();

Assert.AreEqual( 1, records[0].Id );
Assert.AreEqual( "constant", records[0].Name );
Assert.AreEqual( 2, records[1].Id );
Assert.AreEqual( "constant", records[0].Name );
}

private class Test
{
public int Id { get; set; }
public string Name { get; set; }
}

private sealed class TestMap : CsvClassMap<Test>
{
public TestMap()
{
Map( m => m.Id );
Map( m => m.Name ).Constant( "constant" );
}
}
}
}
60 changes: 60 additions & 0 deletions src/CsvHelper.Tests/Writing/ConstantTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CsvHelper.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CsvHelper.Tests.Writing
{
[TestClass]
public class ConstantTests
{
[TestMethod]
public void SameValueIsWrittenForEveryRecordTest()
{
using( var stream = new MemoryStream() )
using( var reader = new StreamReader( stream ) )
using( var writer = new StreamWriter( stream ) )
using( var csv = new CsvWriter( writer ) )
{
var records = new List<Test>
{
new Test { Id = 1, Name = "one" },
new Test { Id = 2, Name = "two" }
};

csv.Configuration.RegisterClassMap<TestMap>();
csv.WriteRecords( records );
writer.Flush();
stream.Position = 0;

var expected = new StringBuilder();
expected.AppendLine( "Id,Name" );
expected.AppendLine( "1,constant" );
expected.AppendLine( "2,constant" );

var result = reader.ReadToEnd();

Assert.AreEqual( expected.ToString(), result );
}
}

private class Test
{
public int Id { get; set; }
public string Name { get; set; }
}

private sealed class TestMap : CsvClassMap<Test>
{
public TestMap()
{
Map( m => m.Id );
Map( m => m.Name ).Constant( "constant" );
}
}
}
}
32 changes: 31 additions & 1 deletion src/CsvHelper/Configuration/CsvPropertyMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,39 @@ public virtual CsvPropertyMap Ignore( bool ignore )
/// the CSV field is empty.
/// </summary>
/// <param name="defaultValue">The default value.</param>
public virtual CsvPropertyMap Default( object defaultValue )
public virtual CsvPropertyMap Default<T>( T defaultValue )
{
var returnType = typeof( T );
if( !Data.Property.PropertyType.IsAssignableFrom( returnType ) )
{
throw new CsvConfigurationException( $"Default type '{returnType.FullName}' cannot be assigned to property type '{Data.Property.PropertyType.FullName}'." );
}

Data.Default = defaultValue;
Data.IsDefaultSet = true;

return this;
}

/// <summary>
/// The constant value that will be used when reading.
/// This value will always be used no matter what other
/// mapping configurations are specified.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="constantValue"></param>
/// <returns></returns>
public virtual CsvPropertyMap Constant<T>( T constantValue )
{
var returnType = typeof( T );
if( !Data.Property.PropertyType.IsAssignableFrom( returnType ) )
{
throw new CsvConfigurationException( $"Constant type '{returnType.FullName}' cannot be assigned to property type '{Data.Property.PropertyType.FullName}'." );
}

Data.Constant = constantValue;
Data.IsConstantSet = true;

return this;
}

Expand Down
14 changes: 5 additions & 9 deletions src/CsvHelper/Configuration/CsvPropertyMapData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,7 @@ public class CsvPropertyMapData
/// <summary>
/// Gets or sets the default value used when a CSV field is empty.
/// </summary>
public virtual object Default
{
get { return defaultValue; }
set
{
defaultValue = value;
IsDefaultSet = true;
}
}
public virtual object Default { get; set; }

/// <summary>
/// Gets or sets a value indicating whether this instance is default value set.
Expand All @@ -96,6 +88,10 @@ public virtual object Default
/// </summary>
public virtual bool IsDefaultSet { get; set; }

public virtual object Constant { get; set; }

public virtual bool IsConstantSet { get; set; }

#if !NET_2_0

/// <summary>
Expand Down
8 changes: 6 additions & 2 deletions src/CsvHelper/CsvReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1519,7 +1519,7 @@ protected virtual void AddPropertyBindings( CsvPropertyMapCollection properties,
continue;
}

var index = -1;
int index;
if( propertyMap.Data.IsNameSet || configuration.HasHeaderRecord && !propertyMap.Data.IsIndexSet )
{
// Use the name.
Expand Down Expand Up @@ -1553,7 +1553,11 @@ protected virtual void AddPropertyBindings( CsvPropertyMapCollection properties,
Expression typeConverterFieldExpression = Expression.Call( typeConverterExpression, "ConvertFromString", null, fieldExpression, Expression.Constant( this ), Expression.Constant( propertyMap.Data ) );
typeConverterFieldExpression = Expression.Convert( typeConverterFieldExpression, propertyMap.Data.Property.PropertyType );

if( propertyMap.Data.IsDefaultSet )
if( propertyMap.Data.IsConstantSet )
{
fieldExpression = Expression.Constant( propertyMap.Data.Constant );
}
else if( propertyMap.Data.IsDefaultSet )
{
// Create default value expression.
Expression defaultValueExpression = Expression.Convert( Expression.Constant( propertyMap.Data.Default ), propertyMap.Data.Property.PropertyType );
Expand Down
43 changes: 26 additions & 17 deletions src/CsvHelper/CsvWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,30 +758,39 @@ protected virtual Delegate CreateActionForObject( Type type )
continue;
}

if( propertyMap.Data.TypeConverter == null )
Expression fieldExpression;

if( propertyMap.Data.IsConstantSet )
{
// Skip if the type isn't convertible.
continue;
fieldExpression = Expression.Constant( propertyMap.Data.Constant );
}
else
{
if( propertyMap.Data.TypeConverter == null )
{
// Skip if the type isn't convertible.
continue;
}

var fieldExpression = CreatePropertyExpression( recordParameter, configuration.Maps[type], propertyMap );
fieldExpression = CreatePropertyExpression( recordParameter, configuration.Maps[type], propertyMap );

var typeConverterExpression = Expression.Constant( propertyMap.Data.TypeConverter );
if( propertyMap.Data.TypeConverterOptions.CultureInfo == null )
{
propertyMap.Data.TypeConverterOptions.CultureInfo = configuration.CultureInfo;
}
var typeConverterExpression = Expression.Constant( propertyMap.Data.TypeConverter );
if( propertyMap.Data.TypeConverterOptions.CultureInfo == null )
{
propertyMap.Data.TypeConverterOptions.CultureInfo = configuration.CultureInfo;
}

propertyMap.Data.TypeConverterOptions = TypeConverterOptions.Merge( propertyMap.Data.TypeConverterOptions, configuration.TypeConverterOptionsFactory.GetOptions( propertyMap.Data.Property.PropertyType ), propertyMap.Data.TypeConverterOptions );
propertyMap.Data.TypeConverterOptions = TypeConverterOptions.Merge( propertyMap.Data.TypeConverterOptions, configuration.TypeConverterOptionsFactory.GetOptions( propertyMap.Data.Property.PropertyType ), propertyMap.Data.TypeConverterOptions );

var method = propertyMap.Data.TypeConverter.GetType().GetMethod( "ConvertToString" );
fieldExpression = Expression.Convert( fieldExpression, typeof( object ) );
fieldExpression = Expression.Call( typeConverterExpression, method, fieldExpression, Expression.Constant( this ), Expression.Constant( propertyMap.Data ) );
var method = propertyMap.Data.TypeConverter.GetType().GetMethod( "ConvertToString" );
fieldExpression = Expression.Convert( fieldExpression, typeof( object ) );
fieldExpression = Expression.Call( typeConverterExpression, method, fieldExpression, Expression.Constant( this ), Expression.Constant( propertyMap.Data ) );

if( type.GetTypeInfo().IsClass )
{
var areEqualExpression = Expression.Equal( recordParameter, Expression.Constant( null ) );
fieldExpression = Expression.Condition( areEqualExpression, Expression.Constant( string.Empty ), fieldExpression );
if( type.GetTypeInfo().IsClass )
{
var areEqualExpression = Expression.Equal( recordParameter, Expression.Constant( null ) );
fieldExpression = Expression.Condition( areEqualExpression, Expression.Constant( string.Empty ), fieldExpression );
}
}

var writeFieldMethodCall = Expression.Call( Expression.Constant( this ), "WriteConvertedField", null, fieldExpression );
Expand Down

0 comments on commit 8638099

Please sign in to comment.