使用 CsvHelper 时转换为十进制

Posted

技术标签:

【中文标题】使用 CsvHelper 时转换为十进制【英文标题】:Convert to decimal when using CsvHelper 【发布时间】:2021-12-01 11:13:44 【问题描述】:

当我尝试使用 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,,,,

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

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: "无法执行转换。\n 文本: ''\n MemberType: System.Decimal\n TypeConverter: 'CsvHelper.TypeConversion.DecimalConverter'\nIReader 状态:\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","增值税基础","LensBespokePrice","CareOnly","Notes"] \nIParser 状态:\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,用户,测试,测试用户,17/09/1957,64,DAILIES® AquaComfort PLUS 30 包,DAILIES® AquaComfort PLUS 30 包,16.5,16.5,33,6.35,,,,\r\n\n" 在 CsvHelper.TypeConversion.DefaultTypeConverter.ConvertFromString(字符串文本,IReaderRow 行,MemberMapData memberMapData)\n 在 CsvHelper.TypeConversion.DecimalConverter.ConvertFromString(字符串文本,IReaderRow 行,MemberMapData memberMapData)\n 在 CsvHelper.Expressions.RecordCreator.CreateT\n在 CsvHelper.Expressions.RecordManager.CreateT\n 在 CsvHelper.CsvReader.d__87`1.MoveNext()\n 在 /Users/abhi/Documents/Practice/dd-journal/ 中的 dd_journal.Program.Main(String[] args) Program.cs:22

【问题讨论】:

【参考方案1】:

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

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

您可以修改 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; 

    

请注意,我在Main 方法内的foreach 循环中添加了JsonSerializer.Serialize(payer);,以便您可以从控制台查看JSON 结果。

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

澄清为什么这只发生在您的foreach 循环而不是var records = csv.GetRecords&lt;Payer&gt;(); 是因为在枚举记录之前这些值实际上并未转换为您的payer 类。

【讨论】:

谢谢,现在一切正常!我有点费力地理解错误输出,但现在我已经有了一个例子,现在更清楚了。【参考方案2】:

Payer 是一整类值。当您写入控制台时,它似乎正在尝试转换。我相信您需要告诉它您想要打印的付款人的哪一部分:

Console.WriteLine(payer.surname);

【讨论】:

虽然这绝对是正确的,但问题在于尝试将空值转换为小数。

以上是关于使用 CsvHelper 时转换为十进制的主要内容,如果未能解决你的问题,请参考以下文章

编写时在 CsvHelper 中转换或过滤成员

当十进制为 0 时,DecimalFormat.parse() 的转换结果时发生 ClassCastException

编程算法基础-2.3进制转换

Java 控制台做个十进制和二进制互相转换

从Greenplum读取数据时,如何在火花中将十进制值转换为字符串?

使用 Javascript 将十进制转换为十六进制或 Unicode