Csvhelper: Using ShouldQuote(): how to get the class map information from the context parameter?

Created on 15 Apr 2020  Â·  41Comments  Â·  Source: JoshClose/CsvHelper

I am struggling with the usage of ShouldQuote... Is it possible to extract from context parameter the information about the class map for the different columns from a record?
I would like to use ShouldQuote to quote only the columns mapped to a String type.

Most helpful comment

I'm opening this again so I don't lose this feature.

All 41 comments

I have the exact same question.

This appears to be related to this question. https://github.com/JoshClose/CsvHelper/issues/693

I don't believe it is possible in ShouldQuote to determine the object type of the value. Here is a possible solution to the problem. https://stackoverflow.com/questions/56579848/csvhelper-configuration-shouldquote-return-true-for-only-fields-on-the-dto-whi

I don't believe it is possible in ShouldQuote to determine the object type of the value. Here is a possible solution to the problem. https://stackoverflow.com/questions/56579848/csvhelper-configuration-shouldquote-return-true-for-only-fields-on-the-dto-whi

Thats the workaround I am actually using.
But it should be possible to get the mapped property type from the name of the field, when handling the header (this can be identified from the context, when "HasHeaderBeenWritten" is true). This information should then be somehow cached when writing the subsequent records. Although I noticed another problem when writing a csv with several headers: the parameter "HasHeaderBeenWritten" is set on true only after writing the first header, and then it is set on false. When writing the second header, the "HasHeaderBeenWritten" remains false...

the parameter "HasHeaderBeenWritten" is set on true only after writing the first header, and then it is set on false. When writing the second header, the "HasHeaderBeenWritten" remains false...

Submit a bug for this.

You can get the index using context.Record and find the map that way. You will need to change this code up if you're using field members or constructor parameters instead of properties. You could also do some checks if you're using a combination of them.

void Main()
{
    var records = new List<Foo>
    {
        new Foo { Id = 1, Name = "one" },
        new Foo { Id = 2, Name = "two" },
    };
    using (var writer = new StringWriter())
    using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
    {
        csv.Configuration.ShouldQuote = (field, context) => 
        {
            var index = context.Record.Count;
            var type = ((PropertyInfo)context.WriterConfiguration.Maps.Find<Foo>().MemberMaps[index].Data.Member).PropertyType;
            if (type == typeof(string))
            {
                return true;
            }

            return ConfigurationFunctions.ShouldQuote(field, context);
        };
        csv.WriteRecords(records);
        writer.ToString().Dump();
    }
}

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Thanks for the code sample

You're welcome.

@AltruCoder I updated your answer on SO and added the ShouldQuote example.

How would this work when there are multiple record types being written?

@JoshClose : Is there actually any workaround to write only single quotes for a string column?

Right now only these 2 options are possible for string columns when writing a csv file:

  • double quote:
"""Some string column""","""Some other string column""",1123,5.67,999,

or

  • no quote at all:
Some string column,Some other string column,1123,5.67,999,

Would be possible to write a csv like this?

  • single quote:
"Some string column","Some other string column",1123,5.67,999,

@abrasat - I believe that is what Josh's example does.

With Josh's example I get the double (or better said triple) quotes (scenario 1)

@abrasat - There has to be something else going on then. I tried the example and I get scenario 3.

@abrasat - If you output to the console, does it still do triple quotes? I also didn't like how it quoted the two string headers so I added && context.HasHeaderBeenWritten to the if statement.

public class Program
{
    static void Main(string[] args)
    {
        var records = new List<Foo>
        {
            new Foo 
            { 
                SomeStringColumn = "Some string column", 
                SomeOtherStringColumn = "Some other string column",
                Id = 1123,
                DoubleProperty = 5.67D,
                IntProperty = 999
            }
        };
        using (var csv = new CsvWriter(Console.Out, CultureInfo.InvariantCulture))
        {
            csv.Configuration.ShouldQuote = (field, context) =>
            {
                var index = context.Record.Count;
                var type = ((PropertyInfo)context.WriterConfiguration.Maps.Find<Foo>().MemberMaps[index].Data.Member).PropertyType;
                if (type == typeof(string) && context.HasHeaderBeenWritten)
                {
                    return true;
                }

                return ConfigurationFunctions.ShouldQuote(field, context);
            };
            csv.WriteRecords(records);

            Console.ReadKey();
        }
    }
}

public class Foo
{
    public string SomeStringColumn { get; set; }
    public string SomeOtherStringColumn { get; set; }
    public int Id { get; set; }
    public double DoubleProperty { get; set; }
    public int IntProperty { get; set; }
}

You are right, it works!
I was actually using the QuoteStringConverter from the SO example

    csv.Configuration.TypeConverterCache.AddConverter<string>(new QuoteStringConverter());

but my mistake was that I forgot to add this line before setting the converter:

    csv.Configuration.ShouldQuote = (field, context) => false;

It would be useful to have the option to decide for each column having the string type if or how to quote the values, not only globally for all string columns

There was an example here https://github.com/JoshClose/CsvHelper/issues/1221#issuecomment-457288093 for quoting specific columns.

Thanks. How would this work when using the QuoteStringConverter instead of ShouldQuote ?
And also would be nice to get the column header names instead of the column index

How would this work when there are multiple record types being written?

Still interested if this is possible.

@JoshClose the solution you gave here for getting the current field's type only works when there is a type to be mapped.
This is not always the case (eg: I'm exporting a DataTable to CSV and using your code example from here).
Would it be possible to add the original field type to the ShouldQuote delegate signature?

What are you doing that you need to know the type to know if the field needs to be quoted?

Thanks for the quick reply.
I have an integration requirement with an external system that all string values must be quoted.
I found a workaround for this (checking the type directly with the data table).
I still think it can be a helpful feature since CSV consumers (both humans and machines) can be very particular as to what they want (eg: a customer annoyed by Excel misinterpreting a decimal or a date is a regular occurrence that can be solved by quoting the values (with its own downside of losing the type inference by Excel)).

Is there a case where you couldn't quote all fields?

I have the same requirement, just string fields need to be quoted and no others.
I also have multiple record types.

Yes, I forgot to mention. I specifically should not quote any of the other
field types.

On Tue, 19 Jan 2021 at 22:26, Josh Close notifications@github.com wrote:

Is there a case where you couldn't quote all fields?

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/JoshClose/CsvHelper/issues/1503#issuecomment-763110730,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAOJ6CCM77LZWNKBWB4URZLS2XTJDANCNFSM4MIKTQNQ
.

There is a feature request to add ShouldQuote(bool) to mappings (and attributes) to specify a field should always be quoted or not. This is probably a better idea than passing more information into the ShouldQuote callback. ShouldQuote is also called when doing WriteField(string field), so there is no type in that case. This might be a good case to add that feature. https://github.com/JoshClose/CsvHelper/issues/1644

I saw that feature request and it's IMO only relevant for simple cases where the types are well defined and limited in size.
My code gets DataTables for reports from different sources representing different models and it has no knowledge about the contents so this solution will not help.
I do see your point regarding ShouldQuote (I dug through the code).

The type isn't known at the time that is called. https://github.com/JoshClose/CsvHelper/blob/8b555ec2cf5a918419f13f3f750e11956786209b/src/CsvHelper/CsvWriter.cs#L140

I'll have to think about this. Maybe I can add something to the map collection to easily get the info you're looking for.

I really appreciate that.
Would the map collection hold this info even if there is no concrete type used for records (like the DataTable based code I'm using)?
Again, for now, I have a workaround that seems to be working.

What do you think of this. Added List<Type> IWriterRow.FieldTypes. In ShouldQuote you can use IWriterRow.Index to get the current type.

ShouldQuote = (field, row) => row.FieldTypes[row.Index] == typeof(string);

You'll have access to the previous types then if needed also.

Maybe that's too confusing. Maybe it should just be IWriterRow.FieldType and it applies to the current field.

Could the field parameter be updated to an object which holds the string value and the Type?
In any case I'm really not too fussed, any solution that provides the type per field while working with multiple records is good.

ShouldQuote already has the string field as a parameter.

Yes.. I am suggesting if that could be replaced with an object, which still holds the string field, but also contains the Type.
Since you are debating on how best to include the field Type within an object that holds Row information I am suggesting maybe it is better located close to the field value that is already passed in.

Ah I see. I'll have to think about that.

Im getting ShouldQuote is read only on the latest version

image

Would this be the new way to set ShouldQuote to false?

image

@accessfreelancer the short answer is yes. Check out Josh's comment here https://github.com/JoshClose/CsvHelper/issues/1664#issuecomment-764714249

Ah I see. I'll have to think about that.

I like @TimSirmovics's suggestion for its clarity and future maintainability (if similar per-field requirements pop up), but like he said your suggestions are just fine and not at all confusing.

I'm opening this again so I don't lose this feature.

Was this page helpful?
0 / 5 - 0 ratings