使用 CsvHelper 时转换为十进制

Convert to decimal when using CsvHelper

当我尝试使用 CsvHelper 解析以下 CSV 时,出现“无法执行转换”错误(完整错误如下)。看起来我遗漏了一些有关如何将读取值作为小数处理的信息。看到了一些与设置文化有关的其他答案,但这似乎没有帮助。

CSV 数据为:

Title,Amount,NHS,Reference,GoCardless ID,email,surname,firstname,Full Name,DOB,Age,Right Lens,Left Lens,RightLensMonthlyAmount,LeftLensMonthlyAmount,LensMonthlyAmount,FeeMonthlyAmount,VAT Basis,LensBespokePrice,CareOnly,Notes
Mrs,24.3,N,100247,CUXXX,email@gmail.com,User,Test,Test User,17/09/1957,64,DAILIES® AquaComfort PLUS 30 Pack,DAILIES® AquaComfort PLUS 30 Pack,16.5,16.5,33,6.35,,,,

我的 class 将此数据映射到属性是:

public class Payer
    {
        public string Title { get; set; }
        public decimal Amount { get; set; }

        [BooleanTrueValues("Y")]
        [BooleanFalseValues("N")]
        public bool NHS { get; set; }

        public string Reference { get; set; }

        [Name("GoCardless ID")]
        public string GoCardless_ID { get; set; }

        public string email { get; set; }
        public string surname { get; set; }
        public string firstname { get; set; }

        [Name("Full Name")]
        public string Fullname { get; set; }

        [Name("DOB")]
        public string Dob { get; set; }

        public int Age { get; set; }

        [Name("Right Lens")]
        public string RightLens { get; set; }
        [Name("Left Lens")]
        public string LeftLens { get; set; }

        public decimal RightLensMonthlyAmount { get; set; }
        public decimal LeftLensMonthlyAmount { get; set; }
        public decimal LensMonthlyAmount { get; set; }
        public decimal FeeMonthlyAmount { get; set; }

        [Name("VAT Basis")]
        public string VATBasis { get; set; }

        public decimal LensBespokePrice { get; set; }

        [BooleanTrueValues("Y")]
        public bool CareOnly { get; set; }

    }

我与解析 CSV 相关的代码是:

static void Main(string[] args)
    {
        var culture = new CultureInfo("en-GB");
        var config = new CsvHelper.Configuration.CsvConfiguration(culture);

        using (var reader = new StreamReader("test.csv"))
        using (var csv = new CsvReader(reader, config))
        {
            var records = csv.GetRecords<Payer>();
            Console.WriteLine("Got records"); //this prints on the console
            foreach (var payer in records)
            {
                Console.WriteLine(payer);
            }
        }
        

    }

错误只发生在 foreach 循环中,而不是实际的 GetRecords() 方法。

完全错误:

CsvHelper.TypeConversion.TypeConverterException: "The conversion cannot be performed.\n Text: ''\n MemberType: System.Decimal\n TypeConverter: 'CsvHelper.TypeConversion.DecimalConverter'\nIReader state:\n ColumnCount: 0\n CurrentIndex: 18\n HeaderRecord:\n["Title","Amount","NHS","Reference","GoCardless ID","email","surname","firstname","Full Name","DOB","Age","Right Lens","Left Lens","RightLensMonthlyAmount","LeftLensMonthlyAmount","LensMonthlyAmount","FeeMonthlyAmount","VAT Basis","LensBespokePrice","CareOnly","Notes"]\nIParser state:\n ByteCount: 0\n CharCount: 392\n Row: 2\n RawRow: 2\n Count: 21\n RawRecord:\nMrs,24.3,N,100247,CUXXX,email@gmail.com,User,Test,Test User,17/09/1957,64,DAILIES® AquaComfort PLUS 30 Pack,DAILIES® AquaComfort PLUS 30 Pack,16.5,16.5,33,6.35,,,,\r\n\n" at CsvHelper.TypeConversion.DefaultTypeConverter.ConvertFromString(String text, IReaderRow row, MemberMapData memberMapData)\n at CsvHelper.TypeConversion.DecimalConverter.ConvertFromString(String text, IReaderRow row, MemberMapData memberMapData)\n at CsvHelper.Expressions.RecordCreator.CreateT\n at CsvHelper.Expressions.RecordManager.CreateT\n at CsvHelper.CsvReader.d__87`1.MoveNext()\n at dd_journal.Program.Main(String[] args) in /Users/abhi/Documents/Practice/dd-journal/Program.cs:22

Payer 是一个完整的 class 值。看起来它在您写入控制台时正在尝试转换。我相信你需要告诉它你想要打印付款人的哪一部分:

Console.WriteLine(payer.surname);

所以问题是 CSVHelper 不理解如何将 LensBespokePrice 的空字段转换为十进制值。您可以在此处使用两个选项:

  1. 更新 CSV 文件以将默认值添加到空字段(即 LensBespokePrice 为 0)。
  2. 创建类型转换以将空单元格处理为小数。

你有能力修改CSV文件吗?如果是这样,那么#1 通过将您的 CSV 更改为(注意 LensBespokePriceCareOnly 的更改)来工作:

Title,Amount,NHS,Reference,GoCardless ID,email,surname,firstname,Full Name,DOB,Age,Right Lens,Left Lens,RightLensMonthlyAmount,LeftLensMonthlyAmount,LensMonthlyAmount,FeeMonthlyAmount,VAT Basis,LensBespokePrice,CareOnly,Notes
Mrs,24.3,N,100247,CUXXX,email@gmail.com,User,Test,Test User,17/09/1957,64,DAILIES® AquaComfort PLUS 30 Pack,DAILIES® AquaComfort PLUS 30 Pack,16.5,16.5,33,6.35,,0,N,

如果不是,您将需要一个 type converter 来表示空的十进制数和空的布尔值。例如,将所有代码放在一个文件中,可能如下所示:

using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.Configuration.Attributes;
using CsvHelper.TypeConversion;
using System;
using System.Globalization;
using System.IO;
using System.Text.Json;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var culture = new CultureInfo("en-GB");
            var config = new CsvConfiguration(culture);
            

            using (var reader = new StreamReader("test.csv"))
            using (var csv = new CsvReader(reader, config))
            {
                var records = csv.GetRecords<Payer>();
                Console.WriteLine("Got records"); //this prints on the console
                foreach (var payer in records)
                {
                    var j = JsonSerializer.Serialize(payer);
                    Console.WriteLine(j);
                }
            }
        }
    }

    public class CustomDecimalConverter : DecimalConverter
    {
        public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
        {
            if(decimal.TryParse(text, out var result))
            {
                return result;
            } else
            {
                return decimal.Zero;
            }
        }
    }

    public class CustomBooleanConverter : BooleanConverter
    {
        public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
        {
            if (bool.TryParse(text, out var result))
            {
                return result;
            }
            else
            {
                return false;
            }
        }
    }

    public class Payer
    {
        public string Title { get; set; }
        public decimal Amount { get; set; }

        [BooleanTrueValues("Y")]
        [BooleanFalseValues("N")]
        public bool NHS { get; set; }

        public string Reference { get; set; }

        [Name("GoCardless ID")]
        public string GoCardless_ID { get; set; }

        public string email { get; set; }
        public string surname { get; set; }
        public string firstname { get; set; }

        [Name("Full Name")]
        public string Fullname { get; set; }

        [Name("DOB")]
        public string Dob { get; set; }

        public int Age { get; set; }

        [Name("Right Lens")]
        public string RightLens { get; set; }
        [Name("Left Lens")]
        public string LeftLens { get; set; }

        public decimal RightLensMonthlyAmount { get; set; }
        public decimal LeftLensMonthlyAmount { get; set; }
        public decimal LensMonthlyAmount { get; set; }
        public decimal FeeMonthlyAmount { get; set; }

        [Name("VAT Basis")]
        public string VATBasis { get; set; }

        [TypeConverter(typeof(CustomDecimalConverter))]
        public decimal LensBespokePrice { get; set; }

        [BooleanTrueValues("Y")]
        [BooleanFalseValues("N")]
        [TypeConverter(typeof(CustomBooleanConverter))]
        public bool CareOnly { get; set; }

    }
}

Please note that I added the JsonSerializer.Serialize(payer); in the foreach loop within the Main method so that you can view the JSON result from the console.

我添加了两个自定义转换器(CustomBooleanConverterCustomDecimalConverter)。然后更新 payer class 以将属性添加到 LensBespokePriceCareOnly 属性。此外,您在 CareOnly 上没有用于假值的属性,虽然不需要,但这是一个很好的做法。

澄清为什么这只发生在你的 foreach 循环而不是 var records = csv.GetRecords<Payer>(); 是因为这些值实际上并没有转换成你的 payer class 直到记录被枚举。