CsvHelper Parsing boolean from String
Asked Answered
B

4

4

I'm currently trying to parse a value from a csv file that is a boolean. We've noticed that the value will successfully parse Yes and Y (in any case) but will not parse No and N

I'm mapping the value in the classmap like this:

Map(m => m.Enabled).Name("Enabled").TypeConverterOption(false, string.Empty);

Is there a reason why this would read Yes but not No and is there a way to add the ability to parse no?

Bogy answered 27/10, 2017 at 8:29 Comment(2)
So you say false should be converted from the empty string, right? Do you have any more configuration going on?Nonsectarian
#2873210Devi
M
8

In version 4.0.3, this works.

void Main()
{
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream))
    using (var reader = new StreamReader(stream))
    using (var csv = new CsvReader(reader))
    {
        writer.WriteLine("Id,Name,IsSomething");
        writer.WriteLine("1,one,Yes");
        writer.WriteLine("2,two,Y");
        writer.WriteLine("3,three,No");
        writer.WriteLine("4,four,N");
        writer.Flush();
        stream.Position = 0;

        csv.Configuration.RegisterClassMap<TestMap>();
        csv.GetRecords<Test>().ToList().Dump();
    }
}

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

public sealed class TestMap : ClassMap<Test>
{
    public TestMap()
    {
        Map(m => m.Id);
        Map(m => m.Name);
        Map(m => m.IsSomething)
            .TypeConverterOption.BooleanValues(true, true, "Yes", "Y")
            .TypeConverterOption.BooleanValues(false, true, "No", "N");
    }
}

In version 2.16.3 this works.

void Main()
{
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream))
    using (var reader = new StreamReader(stream))
    using (var csv = new CsvReader(reader))
    {
        writer.WriteLine("Id,Name,IsSomething");
        writer.WriteLine("1,one,Yes");
        writer.WriteLine("2,two,Y");
        writer.WriteLine("3,three,No");
        writer.WriteLine("4,four,N");
        writer.Flush();
        stream.Position = 0;

        csv.Configuration.RegisterClassMap<TestMap>();
        csv.GetRecords<Test>().ToList().Dump();
    }
}

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

public sealed class TestMap : CsvClassMap<Test>
{
    public TestMap()
    {
        Map(m => m.Id);
        Map(m => m.Name);
        Map(m => m.IsSomething)
            .TypeConverterOption(true, "Yes", "Y")
            .TypeConverterOption(false, "No", "N");
    }
}
Menes answered 27/10, 2017 at 20:21 Comment(1)
In version 4.0.3 , I am wondering why we have to add two "true" instead of one.Avrilavrit
B
3

As an extension method to make the usage a little easier.

Map(x => x.IsActive).Name("Active").WithExtendedBooleanValues();

Tested on v15, but I'm sure it works well before then.

namespace CsvHelper.Configuration
{
    public static class MemberMapExtensions
    {
        /// <summary>
        /// Adds "y", "yes" and "set to yes" and the "no" equivalents to the list of accepted boolean values
        /// for this member map.
        /// </summary>
        public static MemberMap<TClass, TMember> WithExtendedBooleanValues<TClass, TMember>(
            this MemberMap<TClass, TMember> memberMap)
        {
            memberMap
                // Note: It's case insensitive
                // https://github.com/JoshClose/CsvHelper/blob/master/src/CsvHelper/TypeConversion/BooleanConverter.cs
                .TypeConverterOption.BooleanValues(true, clearValues: false, "y", "yes", "set to yes")
                .TypeConverterOption.BooleanValues(false, clearValues: false, "n", "no", "set to no");

            return memberMap;
        }
    }
}
Bern answered 23/11, 2021 at 15:5 Comment(1)
This is the ONLY working solution I've found anywhere.Rox
P
2

We use the following in version 7

Usage for an individual mapping:

Map(m => m.IsSomething).Name("IsSomething").TypeConverter<MyBooleanConverter>();

Usage for applying to the CSV reader

csv.Configuration.TypeConverterCache.AddConverter<bool>(new MyBooleanConverter());

Code:

public sealed class MyBooleanConverter : DefaultTypeConverter
{
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        if(TryParseConvertBoolean(text, out bool result))
        {
            return result;
        }

        return null;
    }

    public static bool TryParseConvertBoolean(string value, out bool result)
    {
        result = false;
        try
        {
            if (string.IsNullOrWhiteSpace(value))
                return false;
            else
            {
                var lower = value.ToLower();

                if (lower == "no" || lower == "n")
                {
                    result = false;
                    return true;
                }
                else if (lower == "yes" || lower == "y")
                {
                    result = true;
                    return true;
                }
            }
        }
        catch { }

        return false;
    }
}
Patrolman answered 26/10, 2018 at 10:44 Comment(1)
The above MyBooleanConverter has a bug and does not work with empty string. When it's empty, the TryParseConvertBoolean() function returns false, causing the caller to skip to the line "return null". And this null result causes parser error.Geriatric
G
0

Above answer has a bug, here is a simplified version I am using and it works well:

public sealed class MyBooleanConverter : DefaultTypeConverter
{
    public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
    {
        if (string.IsNullOrWhiteSpace(text)) return false;
        var lower = text.ToLower();
        if (lower == "true" || lower == "yes" || lower == "y" || lower == "1") return true;
        return false;
    }
}
Geriatric answered 24/7, 2024 at 15:17 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.