65.9K
CodeProject 正在变化。 阅读更多。
Home

Cinchoo ETL - JSON 读取器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2020 年 5 月 22 日

CPOL

35分钟阅读

viewsIcon

28269

.NET 简易 JSON 读取器

目录

1. 引言

ChoETL 是一个用于 .NET 的开源 ETL(提取、转换和加载)框架。它是一个基于代码的库,用于从多个源提取数据,在 .NET 环境中对其进行转换,然后加载到您自己的数据仓库中。您可以快速地将数据加载到数据仓库中。

本文介绍了如何使用 ChoETL 框架提供的 ChoJSONReader 组件。它是一个简单的实用类,用于从文件/源中提取 JSON 数据到对象。

特点

  • 它在底层使用 NewtonSoft.JSON 解析器,在几秒钟内解析 JSON 文件,并且可以处理大型文件而不会出现内存问题。
  • 基于流的解析器能够实现极致的性能、低资源消耗,以及近乎无限的多功能性,可扩展到任何大小的数据文件,甚至数十或数百 GB。
  • 基于事件的数据操作和验证允许在批量插入过程中对数据流进行完全控制。
  • 公开 IEnumarable 对象列表——这通常与 LINQ 查询一起用于投影、聚合和过滤等操作。
  • 支持延迟读取。
  • 支持处理具有特定文化背景的日期、货币和数字格式的文件。
  • 读取文件时,能够识别各种日期、货币、枚举、布尔值和数字格式。
  • 在写入文件时提供对日期、货币、枚举、布尔值、数字格式的精细控制。
  • 详细而健壮的错误处理,让您能够快速找到并修复问题。

2. 要求

此框架库使用 C# 编写,基于 .NET 4.5 Framework / .NET core 2.x。

3. “Hello World!” 示例

  • 打开 VS.NET 2013 或更高版本。
  • 创建一个示例 VS.NET (.NET Framework 4.5) 控制台应用程序项目。
  • 通过 程序包管理器控制台 使用基于 .NET 环境的 Nuget 命令安装 ChoETL
    • Install-Package ChoETL.JSON
    • Install-Package ChoETL.JSON.NETStandard
  • 使用 ChoETL 命名空间

让我们从一个读取包含三个字段的 JSON 文件的简单示例开始。

清单 3.1 示例 JSON 数据文件 (emp.json)
[
    {
        "Id": 1,
        "Name": "Jeanette"
    },
    {
        "Id": 2,
        "Name": "Giavani"
    }
]

有多种方法可以以最小的设置开始 JSON 文件解析。

3.1. 快速加载 - 数据优先方法

这是**零配置**、快速加载 JSON 文件的方式,无需 POCO 对象。下面的示例代码展示了如何加载文件。

清单 3.1.1 使用迭代器加载 JSON 文件
foreach (dynamic e in new ChoJSONReader("emp.json"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddle: https://dotnetfiddle.net/aGzea1

清单 3.1.2 使用循环加载 JSON 文件
var reader = new ChoJSONReader("emp.json");
dynamic rec;

while ((rec = reader.Read()) != null)
{
    Console.WriteLine(rec.Id);
    Console.WriteLine(rec.Name);
}

示例 Fiddle: https://dotnetfiddle.net/wy3g2L

3.2. 代码优先方法

这是另一种**零配置**的方式,通过 POCO 类来解析和加载 JSON 文件。首先,定义一个简单的类来匹配底层的 JSON 文件布局。

清单 3.2.1 简单的 POCO 实体类
public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

上面的类定义了两个属性,分别匹配示例 JSON 文件模板。

清单 3.2.2 加载 JSON 文件
foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddle: https://dotnetfiddle.net/wA6aws

3.3. 配置优先方法

在此模型中,我们定义了 JSON 配置,包含所有必要的解析参数以及与底层 JSON 文件匹配的 JSON 字段。

清单 3.3.1 定义 JSON 配置
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Id"));
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

上面的类定义了两个属性,分别匹配示例 JSON 文件模板。

清单 3.3.2 不带 POCO 对象加载 JSON 文件
foreach (dynamic e in new ChoJSONReader("emp.json", config))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddle: https://dotnetfiddle.net/DCTYbS

清单 3.3.3 带 POCO 对象加载 JSON 文件
foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json", config))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddle: https://dotnetfiddle.net/WpzPvH

3.4. 代码优先声明式配置

这是结合的方法,定义 POCO 实体类以及声明式修饰的 JSON 配置参数。Id 是必需字段,Name 是可选值字段,默认值为"XXXX"。如果Name 不存在,则使用默认值。

清单 3.4.1 定义 POCO 对象
public class EmployeeRec
{
    [ChoJSONRecordField]
    [Required]
    public int Id
    {
        get;
        set;
    }
    [ChoJSONRecordField]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }

    public override string ToString()
    {
        return "{0}. {1}".FormatString(Id, Name);
    }
}

上面的代码说明了如何定义 POCO 对象来承载输入文件中每条记录行的值。首先,使用 ChoJSONRecordFieldAttribute 为每个记录字段定义属性,以限定其为 JSON 记录映射。JSONPath 是一个可选属性。如果未指定,框架会自动发现并从 JSON 属性加载值。Id 带有 RequiredAttribute 修饰,如果值丢失,将抛出异常。Name 使用 DefaultValueAttribute 指定了默认值这意味着如果文件中的 Name JSON 字段为空,则默认值为 'XXXX'。

这非常简单,可以立即提取 JSON 数据。

清单 3.4.2 主方法
foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddle: https://dotnetfiddle.net/GGd9uJ

我们开始创建一个 ChoJSONReader 对象的新实例。仅此而已。所有繁重的解析和将 JSON 数据流加载到对象的工作都在底层由解析器完成。

默认情况下,ChoJSONReader 在加载 JSON 文件时会自动发现并使用默认配置参数。这些可以根据您的需要进行覆盖。以下部分将详细介绍每个配置属性。

4. 读取所有记录

就像设置 POCO 对象匹配 JSON 文件结构一样简单,您可以将整个文件读取为枚举模式。这是延迟执行模式,但在对其进行任何聚合操作时要小心。这将把整个文件记录加载到内存中。

清单 4.1 读取 JSON 文件
foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

清单 4.2 读取 JSON 文件流
foreach (var e in new ChoJSONReader<EmployeeRec>(textReader))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

此模型使您的代码优雅、简洁、易于阅读和维护。还可以利用 LINQ 扩展方法执行分组、连接、投影、聚合等操作。

清单 4.3 使用 LINQ
var list = (from o in new ChoJSONReader<EmployeeRec>("emp.json")
           where o.Name != null && o.Name.StartsWith("R")
           select o).ToArray();

foreach (var e in list)
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

5. 手动读取记录

就像设置 POCO 对象匹配 JSON 文件结构一样简单,您可以将整个文件读取为枚举模式。

清单 5.1 读取 JSON 文件
var reader = new ChoJSONReader<EmployeeRec>("emp.json");
dynamic rec = (object)null;

while ((rec = reader.Read()) != null)
{
    Console.WriteLine(rec.Id);
    Console.WriteLine(rec.Name);
}

示例 Fiddle: https://dotnetfiddle.net/ZTgdbV

6. 自定义 JSON 记录

使用 ChoJSONRecordObjectAttribute,您可以声明式地自定义 POCO 实体对象。

清单 6.1 为每个记录自定义 POCO 对象
[ChoJSONRecordObject]
public class EmployeeRec
{
    [ChoJSONRecordField]
    public int Id { get; set; }
    [ChoJSONRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

以下是可用于自定义文件上 JSON 加载操作的属性。

  • JSONPath - 可选。用于选择要加载的元素的 JSONPath 表达式。如果未指定,则会自动发现并加载成员值。
  • CultureName - 文化名称(例如,en-US, en-GB)用于读写 JSON 数据。
  • Encoding - JSON 文件的编码。
  • ColumnCountStrict - 此标志指示在读取预期字段丢失时是否应抛出异常。
  • ErrorMode - 此标志指示在读取时是否应抛出异常,并且预期的字段加载失败。这可以每隔属性覆盖。可能的值是
    • IgnoreAndContinue - 忽略错误,跳过记录并继续处理下一条。
    • ReportAndContinue - 如果 POCO 实体是 IChoNotifyRecordRead 类型,则向其报告错误
    • ThrowAndStop - 抛出错误并停止执行
  • IgnoreFieldValueMode - 一个标志,告知读取器在读取时是否应跳过空/null 的记录。这可以每隔属性覆盖。可能的值是
    • Null - 如果记录值为 null,则跳过
    • DBNull - N/A
    • Empty - 如果记录值为空,则跳过
    • WhiteSpace - 如果记录值仅包含空格,则跳过
  • ObjectValidationMode - 一个标志,告知读取器关于记录对象要执行的验证类型。可能的值是
    • Off - 不执行对象验证(默认)
    • MemberLevel - 在加载每个 JSON 属性及其值时执行验证
    • ObjectLevel - 在所有属性加载到 POCO 对象后执行验证

7. 自定义 JSON 字段

对于每个 JSON 字段,您可以使用 ChoJSONRecordFieldAttribute 在 POCO 实体属性中指定映射仅当您要使用自定义 JSONPath 映射到此字段时,才使用此属性。

清单 7.1 自定义 POCO 对象以匹配 JSON 字段
public class EmployeeRec
{
    [ChoJSONRecordField]
    public int Id { get; set; }
    [ChoJSONRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

以下是您可以为每个属性添加自定义的可用成员

  • JSONPath - 可选。JSONPath 表达式使用路径表示法来寻址 JSON 文档的部分。如果未指定,ChoJSONReader 会自动发现并从匹配字段名称的 JSON 对象加载值。

7.1. 默认值

当 JSON 值为空或为空白时(通过 IgnoreFieldValueMode 控制),用于并设置到属性的值。

可以使用 System.ComponentModel.DefaultValueAttribute 为任何 POCO 实体属性指定默认值。

7.2. 后备值

当 JSON 值未能设置时,用于并设置到属性的值。仅当 ErrorModeIgnoreAndContinueReportAndContinue 时,才会设置 Fallback 值。

可以使用 ChoETL.ChoFallbackValueAttribute 为任何 POCO 实体属性指定回退值。

7.3. 类型转换器

大多数基本类型都会自动转换并设置到属性。如果 JSON 字段的值无法自动转换为属性的类型,您可以指定自定义/内置 .NET 转换器来转换该值。这些可以是 IValueConverterTypeConverter 转换器。

有几种方法可以为每个字段指定转换器

  • 声明式方法
  • 配置方法

7.3.1. 声明式方法

此模型仅适用于 POCO 实体对象。如果您有 POCO 类,您可以为每个属性指定转换器以执行必要的转换。下面的示例展示了如何操作。

清单 7.3.1.1 指定类型转换器
public class EmployeeRec
{
    [ChoJSONRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    public int Id { get; set; }
    [ChoJSONRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}
清单 7.3.1.2 IntConverter 实现
public class IntConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
                          object parameter, CultureInfo culture)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType,
                              object parameter, CultureInfo culture)
    {
        return value;
    }
}

在上面的示例中,我们定义了自定义 IntConverter 类。并展示了如何将其用于 'Id' JSON 属性。

7.3.2. 配置式方法

此模型适用于动态和 POCO 实体对象。这允许在运行时将转换器附加到每个属性。这会优先于 POCO 类上的声明式转换器。

清单 7.3.2.2 指定类型转换器
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();

ChoJSONRecordFieldConfiguration idConfig = new ChoJSONRecordFieldConfiguration("Id");
idConfig.AddConverter(new IntConverter());
config.JSONRecordFieldConfigurations.Add(idConfig);

config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

在上面,我们使用 ChoJSONRecordFieldConfiguration 对象中的 AddConverter 辅助方法构造并附加了 IntConverter 到 'Id' 字段。

同样,如果您想从中删除任何转换器,可以使用 ChoJSONRecordFieldConfiguration 对象上的 RemoveConverter

7.4. 验证

ChoJSONReader 利用 System.ComponentModel.DataAnnotationsValidation Block 验证属性来为 POCO 实体的各个字段指定验证规则。有关可用的 DataAnnotations 验证属性列表,请参阅 MSDN 网站。

清单 7.4.1 在 POCO 实体中使用验证属性
[ChoJSONRecordObject]
public partial class EmployeeRec
{
    [ChoJSONRecordField(FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoJSONRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
}

在上面的示例中,我们为 Id 属性使用了 Range 验证属性。Required 验证属性用于 Name 属性。ChoJSONReader 会根据 Configuration.ObjectValidationMode 设置为 ChoObjectValidationMode.MemberLevelChoObjectValidationMode.ObjectLevel 在加载期间执行验证。

有时,您可能希望覆盖 POCO 类自带的声明式验证行为,可以使用 Cinchoo ETL 通过配置方法来实现。下面的示例展示了如何覆盖它们。

static void ValidationOverridePOCOTest()
{
    ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
    var idConfig = new ChoJSONRecordFieldConfiguration("Id");
    idConfig.Validators = new ValidationAttribute[] { new RequiredAttribute() };
    config.JSONRecordFieldConfigurations.Add(idConfig);
    config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

    using (var parser = new ChoJSONReader<EmployeeRec>("emp.json", config))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

在某些情况下,您可能希望获得控制权并在 POCO 实体类中执行手动**自验证**。这可以通过继承 POCO 对象并实现 IChoValidatable 接口来实现。

清单 7.4.2 在 POCO 实体上手动验证
[ChoJSONRecordObject]
public partial class EmployeeRec : IChoValidatable
{
    [ChoJSONRecordField(FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoJSONRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }

    public bool TryValidateFor(object target, string memberName, 
                               ICollection<ValidationResult> validationResults)
    {
        return true;
    }

    public void Validate(object target)
    {
    }

    public void ValidateFor(object target, string memberName)
    {
    }
}

上面的示例展示了如何在 POCO 对象中实现自定义自验证。

IChoValidatable 接口公开以下方法

  • TryValidate - 验证整个对象,如果所有验证都通过则返回 true。否则返回 false
  • Validate - 验证整个对象,如果验证未通过则抛出异常。
  • TryValidateFor - 验证对象的特定属性,如果所有验证都通过则返回 true。否则返回 false
  • ValidateFor - 验证对象的特定属性,如果验证未通过则抛出异常。

10. 回调机制

ChoJSONReader 开箱即用地提供行业标准的 JSON 解析,以满足大多数解析需求。如果解析不能满足任何需求,您可以使用 ChoJSONReader 提供的回调机制来处理这种情况。为了参与回调机制,您可以使用以下任一模型

  • 使用 ChoJSONReader 通过 IChoReader 接口公开的事件处理程序。
  • 继承 POCO 实体对象,实现 IChoNotifyRecordRead / IChoNotifyFileRead / IChoNotifyRecordFieldRead 接口
  • 继承 DataAnnotationMetadataType 类型对象,并实现 IChoNotifyRecordRead / IChoNotifyFileRead / IChoNotifyRecordFieldRead 接口。
  • 继承 IChoNotifyRecordFieldConfigurable / IChoNotifyRecordFieldConfigurable 配置接口

注意从此接口方法中引发的任何异常都将被忽略。

IChoReader 公开了以下事件

  • BeginLoad - 在 JSON 文件加载开始时调用
  • EndLoad - 在 JSON 文件加载结束时调用
  • BeforeRecordLoad - 在 JSON 记录加载之前触发
  • AfterRecordLoad - 在 JSON 记录加载之后触发
  • RecordLoadError - 在 JSON 记录加载出错时触发
  • BeforeRecordFieldLoad - 在 JSON 字段值加载之前触发
  • AfterRecordFieldLoad - 在 JSON 字段值加载之后触发
  • RecordFieldLoadError - 在 JSON 字段值加载出错时触发
  • SkipUntil- 在 JSON 解析启动之前触发,以添加自定义逻辑来跳过记录行。
  • DoWhile- 在 JSON 解析期间触发,您可以在其中添加自定义逻辑来停止解析。

IChoNotifyRecordRead 公开了以下方法

  • BeforeRecordLoad - 在 JSON 记录加载之前触发
  • AfterRecordLoad - 在 JSON 记录加载之后触发
  • RecordLoadError - 在 JSON 记录加载出错时触发

IChoNotifyFileRead 公开了以下方法

  • BeginLoad - 在 JSON 文件加载开始时调用
  • EndLoad - 在 JSON 文件加载结束时调用
  • SkipUntil - 在 JSON 解析启动之前触发,以添加自定义逻辑来跳过记录行。
  • DoWhile - 在 JSON 解析期间触发,您可以在其中添加自定义逻辑来停止解析。

IChoNotifyRecordFieldRead 公开以下方法

  • BeforeRecordFieldLoad - 在 JSON 字段值加载之前触发
  • AfterRecordFieldLoad - 在 JSON 字段值加载之后触发
  • RecordFieldLoadError - 在 JSON 字段值加载出错时触发

IChoNotifyRecordConfigurable 公开了以下方法

  • RecondConfigure - 在 JSON 记录配置时触发

IChoNotifyRecordFieldConfigurable 公开了以下方法

  • RecondFieldConfigure - 在每个 JSON 记录字段配置时触发

10.1. 使用 ChoJSONReader 事件

这是更直接、最简单的方式,可以订阅回调事件并处理解析 JSON 文件中的特殊情况。缺点是代码不如通过为 POCO 记录对象实现 IChoNotifyRecordRead 那样可重用。

下面的示例展示了如何使用 BeforeRecordLoad 回调方法来跳过以 '%' 字符开头的行。

清单 10.1.1 使用 ChoJSONReader 回调事件
static void IgnoreLineTest()
{
    using (var parser = new ChoJSONReader("emp.json"))
    {

        parser.BeforeRecordLoad += (o, e) =>
        {
            if (e.Source != null)
            {
                e.Skip = !((JObject)e.Source).ContainsKey("Name");
            }
        };
        foreach (var e in parser)
            Console.WriteLine(e.Dump());
    }
}

同样,您也可以将其他回调方法与 ChoJSONReader 一起使用。

10.2. 实现 IChoNotifyRecordRead 接口

下面的示例展示了如何为直接 POCO 类实现 IChoNotifyRecordRead 接口。

清单 10.2.1 直接 POCO 回调机制实现
[ChoJSONRecordObject]
public partial class EmployeeRec : IChoNotifyRecordRead
{
    [ChoJSONRecordField(FieldName = "Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoJSONRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordLoadError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
}

下面的示例展示了如何通过在 POCO 类上使用 MetadataTypeAttribute 来附加 Metadata 类。

清单 10.2 基于 MetaDataType 的回调机制实现
[ChoJSONRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
    [ChoJSONRecordField(FieldName = "Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoJSONRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordLoadError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
}

[MetadataType(typeof(EmployeeRecMeta))]
public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

下面的示例展示了如何通过在 POCO 类上使用 ChoMetaDataRefTypeAttribute 来为密封类或第三方 POCO 类附加 Metadata 类。

清单 10.2.3 基于 ChoMetaDataRefType 的回调机制实现
[ChoMetadataRefType(typeof(EmployeeRec))]
[ChoJSONRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
    [ChoJSONRecordField(FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoJSONRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordLoadError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
}

public partial class EmployeeRec
{
    public int Id { get; set; }    
    public string Name { get; set; }
}

10.3. BeginLoad

此回调在 JSON 文件加载**开始**时调用一次。source 是 JSON 文件流对象。在这里,您有机会检查 stream,返回 true 继续 JSON 加载。返回 false 停止解析。

清单 10.1.1 BeginLoad 回调示例
public bool BeginLoad(object source)
{
    StreamReader sr = source as StreamReader;
    return true;
}

10.4. EndLoad

此回调在 JSON 文件加载**结束**时调用一次。source 是 JSON 文件流对象。在这里,您有机会检查流,对流执行任何后续步骤。

清单 10.2.1 EndLoad 回调示例
public void EndLoad(object source)
{
    StreamReader sr = source as StreamReader;
}

10.5. BeforeRecordLoad

此回调在 JSON 文件中的每个 JSON 节点加载**之前**调用。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。source 是 JSON 记录对象。在这里,您有机会检查对象,如果需要,可以用新值覆盖它。

提示如果要跳过 JObject 的加载,请将 source 设置为 null。

返回 true 继续加载过程,否则返回 false 停止过程。

清单 10.5.1 BeforeRecordLoad 回调示例
public bool BeforeRecordLoad(object target, int index, ref object source)
{
    JObject obj = source as JObject;
    return true;
}

10.6. AfterRecordLoad

此回调在 JSON 文件中的每个 JObject 节点加载**之后**调用。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。source 是 JSON 记录对象。在这里,您有机会对 JObject 行执行任何后续操作。

返回 true 继续加载过程,否则返回 false 停止过程。

清单 10.6.1 AfterRecordLoad 回调示例
public bool AfterRecordLoad(object target, int index, object source)
{
    JObject obj = source as JObject;
    return true;
}

10.7. RecordLoadError

在加载 JObject 节点时遇到error时,将调用此回调。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。sourceJObject 节点。ex 是异常对象。在这里,您有机会处理异常。仅当 Configuration.ErrorMode 设置为 ReportAndContinue 时,才会调用此方法。

返回 true 继续加载过程,否则返回 false 停止过程。

清单 10.7.1 RecordLoadError 回调示例
public bool RecordLoadError(object target, int index, object source, Exception ex)
{
    JObject obj = source as JObject;
    return true;
}

10.8. BeforeRecordFieldLoad

此回调在每个 JSON 记录字段加载**之前**调用。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。propName 是 JSON 记录属性名。value 是 JSON 字段值。在这里,您有机会检查 JSON 记录属性值并执行任何自定义验证等。

返回 true 继续加载过程,否则返回 false 停止过程。

清单 10.8.1 BeforeRecordFieldLoad 回调示例
public bool BeforeRecordFieldLoad(object target, int index, string propName, ref object value)
{
    return true;
}

10.9. AfterRecordFieldLoad

此回调在每个 JSON 记录字段加载**之后**调用。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。propName 是 JSON 记录属性名。value 是 JSON 字段值。可以在此处执行任何后续字段操作,例如计算其他属性、验证等。

返回 true 继续加载过程,否则返回 false 停止过程。

清单 10.9.1 AfterRecordFieldLoad 回调示例
public bool AfterRecordFieldLoad(object target, int index, string propName, object value)
{
    return true;
}

10.10. RecordLoadFieldError

在加载 JSON 记录字段值时遇到error时,将调用此回调。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。propName 是 JSON 记录属性名。value 是 JSON 字段值。ex 是异常对象。在这里,您有机会处理异常。仅在 ChoJSONReader 执行了以下两个序列的步骤后,才会调用此方法。

  • ChoJSONReader 查找每个 JSON 属性的 FallbackValue 值。如果存在,它会尝试将其值赋给它。
  • 如果 FallbackValue 值不存在,并且将 Configuration.ErrorMode 指定为 ReportAndContinue,则会执行此回调。

返回 true 继续加载过程,否则返回 false 停止过程。

清单 10.10.1 RecordFieldLoadError 回调示例
public bool RecordFieldLoadError(object target, int index, string propName, 
                                 object value, Exception ex)
{
    return true;
}

10.11. SkipUntil

在 JSON 解析开始时调用此回调,并带有自定义逻辑来跳过节点。index 是文件中的 JObject 节点索引。

返回 true 以跳过行,否则返回 false

清单 10.11.1 SkipUntil 回调示例
public bool SkipUntil(long index, object source)
{
    return false;
}

10.12. DoWhile

在 JSON 解析开始时调用此回调,并带有自定义逻辑来跳过节点。index 是文件中的 JObject 节点索引。

返回 true 以停止解析,否则返回 false

清单 10.12.1 DoWhile 回调示例
public bool DoWhile(long index, object source)
{
    return false;
}

10. 定制

ChoJSONReader 自动检测并从 POCO 实体加载配置的设置。在运行时,您可以在 JSON 解析之前自定义和调整这些参数。ChoJSONReader 公开 Configuration 属性,它是 ChoJSONRecordConfiguration 对象。使用此属性,您可以自定义它们。

清单 10.1 运行时自定义 ChoJSONReader
class Program
{
    static void Main(string[] args)
    {
        using (var parser = new ChoJSONReader<EmployeeRec>("emp.json"))
        {
            object row = null;

            parser.Configuration.JSONPath = "$";
            while ((row = parser.Read()) != null)
                Console.WriteLine(row.ToString());
        }
    }

11. AsDataReader 辅助方法

ChoJSONReader 公开 AsDataReader 辅助方法,以 .NET datareader 对象的形式检索 JSON 记录。DataReader 是快进数据流。此 datareader 可用于一些地方,例如使用 SqlBulkCopy 将数据批量复制到数据库,加载断开连接的 DataTable 等。

清单 11.1 读取为 DataReader 示例
static void AsDataReaderTest()
{
    using (var parser = new ChoJSONReader<EmployeeRec>("emp.json"))
    {
        IDataReader dr = parser.AsDataReader();
        while (dr.Read())
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

示例 Fiddle: https://dotnetfiddle.net/yZrqwK

12. AsDataTable 辅助方法

ChoJSONReader 公开 AsDataTable 辅助方法,以 .NET DataTable 对象的形式检索 JSON 记录。然后,它可以持久化到磁盘,显示在网格/控件中,或像其他对象一样存储在内存中。

清单 12.1 读取为 DataTable 示例
static void AsDataTableTest()
{
    using (var parser = new ChoJSONReader<EmployeeRec>("emp.json"))
    {
        DataTable dt = parser.AsDataTable();
        foreach (DataRow dr in dt.Rows)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

示例 Fiddle: https://dotnetfiddle.net/JYpII7

13. 使用动态对象

到目前为止,本文介绍了如何将 ChoJSONReader 与 POCO 对象一起使用。ChoJSONReader 也支持在没有 POCO 对象的情况下加载 JSON 文件。它利用 .NET 动态功能。下面的示例展示了如何在没有 POCO 对象的情况下读取 JSON 流。

如果您有 JSON 文件,可以以最小/零配置解析和加载文件。

下面的示例展示了这一点

清单 13.1 加载 JSON 文件
class Program
{
    static void Main(string[] args)
    {
        dynamic row;
        using (var parser = new ChoJSONReader("emp.json"))
        {
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Id);
            }
        }
    }
}

示例 Fiddle: https://dotnetfiddle.net/3Y6OMn

上面的示例自动发现 JSON 对象成员并解析文件。

您可以通过手动添加字段配置并将它们传递给 ChoJSONReader 进行文件解析来覆盖自动发现字段的默认行为。

示例展示了如何操作。

清单 13.3 带配置加载 JSON 文件
class Program
{
    static void Main(string[] args)
    {
        ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
        config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Id"));
        config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

        dynamic row;
        using (var parser = new ChoJSONReader("emp.json", config))
        {
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Name);
            }
        }
    }
}

示例 Fiddle: https://dotnetfiddle.net/nVGNi6

要完全关闭自动字段发现,您需要将 ChoJSONRecordConfiguration.AutoDiscoverColumns 设置为 false

13.1. DefaultValue

当 JSON 值为空或为空白时(通过 IgnoreFieldValueMode 控制),用于并设置到属性的值。

可以使用 System.ComponentModel.DefaultValueAttribute 为任何 POCO 实体属性指定默认值。

对于动态对象成员或覆盖声明式 POCO 对象成员的默认值规范,您可以通过配置来实现,如下所示:

ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Id"));
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name")
{ DefaultValue = "NoName" })

13.2. ChoFallbackValue

当 JSON 值未能设置时,用于并设置到属性的值。仅当 ErrorModeIgnoreAndContinueReportAndContinue 时,才会设置 Fallback 值。

可以使用 ChoETL.ChoFallbackValueAttribute 为任何 POCO 实体属性指定回退值。

对于动态对象成员或覆盖声明式 POCO 对象成员的回退值,您可以通过配置来实现,如下所示:

ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Id"));
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name")
{ FallbackValue = "Tom" });

13.3. FieldType

在无类型动态对象模型中,读取器读取单个字段值并以 'string' 值填充到动态对象成员中。如果您想强制执行类型并在加载期间进行额外的类型检查,可以通过在字段配置中声明字段类型来实现。

清单 8.5.1 定义 FieldType
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Id")
{ FieldType = typeof(int) });
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

上面的示例显示了为 'Id' 字段将字段类型定义为 'int'。这指示 ChoJSONReader 在分配给它之前解析并将值转换为整数。这种额外的类型安全性可以消除在解析过程中加载到对象中的不正确值。

13.4. 类型转换器

大多数基本类型都会被 ChoJSONReader 自动转换并设置到属性。如果 JSON 字段的值无法自动转换为属性的类型,您可以指定自定义/内置 .NET 转换器来转换该值。这些可以是 IValueConverterTypeConverter 转换器。

在动态对象模型中,您可以通过配置来指定这些转换器。请参阅下面的示例,了解为 JSON 字段指定类型转换器的方法。

清单 13.4.1 指定 TypeConverters
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();

ChoJSONRecordFieldConfiguration idConfig = new ChoJSONRecordFieldConfiguration("Id");
idConfig.AddConverter(new IntConverter());
config.JSONRecordFieldConfigurations.Add(idConfig);

config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

在上面,我们使用 ChoJSONRecordFieldConfiguration 对象中的 AddConverter 辅助方法构造并附加了 IntConverter 到 'Id' 字段。

同样,如果您想从中删除任何转换器,可以使用 ChoJSONRecordFieldConfiguration 对象上的 RemoveConverter

13.5. 验证

ChoJSONReader 利用 System.ComponentModel.DataAnnotationsValidation Block 验证属性来为各个 JSON 字段指定验证规则。有关可用的 DataAnnotations 验证属性列表,请参阅 MSDN 网站。

清单 13.5.1 指定验证
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();

ChoJSONRecordFieldConfiguration idConfig = new ChoJSONRecordFieldConfiguration("Id");
idConfig.Validators = new ValidationAttribute[] { new RangeAttribute(0, 100) };
config.JSONRecordFieldConfigurations.Add(idConfig);

config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

在上面的示例中,我们为 Id 属性使用了 Range 验证属性。ChoJSONReader 会在加载期间根据 Configuration.ObjectValidationMode 设置为 ChoObjectValidationMode.MemberLevelChoObjectValidationMode.ObjectLevel 来执行验证。

附注动态对象模型不支持自验证

14. 处理密封 POCO 对象

如果您已经拥有现有的密封 POCO 对象或来自第三方库的对象,我们可以将其与 ChoJSONReader 一起使用。

清单 14.1 现有密封 POCO 对象
public sealed class ThirdPartyRec
{
    public int Id
    {
        get;
        set;
    }
    public string Name
    {
        get;
        set;
    }
}
清单 14.2 消费 JSON 文件
class Program
{
    static void Main(string[] args)
    {
        using (var parser = new ChoJSONReader<ThirdPartyRec>("emp.json"))
        {
            object row = null;

            while ((row = parser.Read()) != null)
                Console.WriteLine(row.ToString());
        }
    }
}

在这种情况下,ChoJSONReader 会反向发现 JSON 文件中的 JSON 字段并将数据加载到 POCO 对象中。如果 JSON 文件结构与 POCO 对象匹配,则加载将成功,所有相应的数据都将填充到其属性中。如果缺少任何 JSON 字段的属性,ChoJSONReader 会静默忽略它们并继续处理其余的。

您可以通过将 ChoJSONRecordConfiguration.ThrowAndStopOnMissingField 属性设置为 false 来覆盖此行为。在这种情况下,如果 JSON 字段缺少属性,ChoJSONReader 将抛出 ChoMissingRecordFieldException 异常。

15. 异常

ChoJSONReader 在不同情况下会抛出不同类型的异常。

  • ChoParserException - JSON 文件损坏,解析器无法恢复。
  • ChoRecordConfigurationException - 指定了任何无效的配置设置,将引发此异常。
  • ChoMissingRecordFieldException - JSON 字段缺少属性,将引发此异常。

17. 使用 MetadataType 注解

Cinchoo ETL 与数据注释的 MetadataType 模型配合效果更好。这是一种将 MetaData 类附加到数据模型类的方法。在此关联类中,您可以提供数据模型中不存在的附加元数据信息。它的作用是向类添加属性,而无需修改该类。您可以将此参数为单个参数的属性添加到将包含所有属性的类中。这在 POCO 类是自动生成的(通过 Entity Framework、MVC 等)时非常有用。这就是第二个类发挥作用的原因。您可以添加新内容而不触及生成的文件。此外,这通过将关注点分离到多个类来促进模块化。

有关更多信息,请在 MSDN 中搜索。

清单 17.1 MetadataType 注解用法示例
[MetadataType(typeof(EmployeeRecMeta))]
public class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

[ChoJSONRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead, IChoValidatable
{
    [ChoJSONRecordField(FieldName = "id", ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoJSONRecordField(FieldName = "Name")]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordLoadError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }

    public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }

    public bool TryValidateFor(object target, string memberName, 
                               ICollection<ValidationResult> validationResults)
    {
        return true;
    }

    public void Validate(object target)
    {
    }

    public void ValidateFor(object target, string memberName)
    {
    }
}

在上面,EmployeeRec 是数据类。仅包含域特定属性和操作。将其视为一个非常简单的类。

我们将验证、回调机制、配置等分离到元数据类型类 EmployeeRecMeta 中。

18. 配置选项

如果 POCO 实体类是自动生成的类、通过库公开的类或是一个sealed类,那么它限制您使用声明式方式为其附加 JSON 模式定义。在这种情况下,您可以选择以下选项之一来指定 JSON 布局配置:

  • 手动配置
  • 自动映射配置
  • 附加 MetadataType

我将向您展示如何为每种方法配置以下 POCO 实体类。

清单 18.1 密封 POCO 实体类
public sealed class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

18.1 手动配置

从头开始定义一个全新的配置对象,并将所有必要的 JSON 字段添加到 ChoJSONConfiguration.JSONRecordFieldConfigurations 集合属性。此选项为您提供了更大的灵活性来控制 JSON 解析的配置。但缺点是可能会出错,并且如果 JSON 文件布局很大,则难以管理。

清单 18.1.1 手动配置
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Id"));
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

18.2 自动映射配置

这是另一种方法,也是一种不太容易出错的方法,用于自动映射 POCO 实体类的 JSON 字段。

首先,为 EmployeeRec POCO 实体类定义一个模式类,如下所示:

清单 18.2.1 自动映射类
public class EmployeeRecMap
{
    [ChoJSONRecordField(FieldName = "Id")]
    public int Id { get; set; }

    [ChoJSONRecordField(FieldName = "Name")]
    public string Name { get; set; }
}

然后,您可以使用它通过 ChoJSONRecordConfiguration.MapRecordFields 方法自动映射 JSON 字段。

清单 18.2.2 使用自动映射配置
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.MapRecordFields<EmployeeRecMap>();

foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json", config))
    Console.WriteLine(e.ToString());

18.3 附加 MetadataType 类

这是另一种将 MetadataType 类附加到 POCO 实体对象的方法。前一种方法仅处理 JSON 字段的自动映射。其他配置属性,如属性转换器、解析器参数、默认/后备值等,均未考虑。

此模型通过定义 MetadataType 类并声明式地指定 JSON 配置参数来考虑所有内容。当您的 POCO 实体是密封类且**不是部分**类时,此方法很有用。此外,它是配置 POCO 实体 JSON 解析的有利且不太容易出错的方法之一。

清单 18.3.1 定义 MetadataType 类
[ChoJSONRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead, IChoValidatable
{
    [ChoJSONRecordField(FieldName = "Id", ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    public int Id { get; set; }

    [ChoJSONRecordField(FieldName = "Name")]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordLoadError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }

    public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }

    public bool TryValidateFor
    (object target, string memberName, ICollection<ValidationResult> validationResults)
    {
        return true;
    }

    public void Validate(object target)
    {
    }

    public void ValidateFor(object target, string memberName)
    {
    }
}
清单 18.3.2 附加 MetadataType 类
//Attach metadata
ChoMetadataObjectCache.Default.Attach<EmployeeRec>(new EmployeeRecMeta());

foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json"))
    Console.WriteLine(e.ToString()

19. LoadText 辅助方法

这是一个非常巧妙的辅助方法,用于将 JSON 文本字符串解析并加载到对象中。

清单 19.1 使用 LoadText 方法

string txt = @"
[
    {
        "Id": 1,
        "Name": "Jeanette"
    },
    {
        "Id": 2,
        "Name": "Giavani"
    }
]";

foreach (var e in ChoJSONReader.LoadText(txt))
   Console.WriteLine(e.ToStringEx());

20. 高级主题

20.1 覆盖转换器格式规范

Cinchoo ETL 会自动解析并将每个 JSON 字段值无缝转换为相应的 JSON 字段的底层数据类型。大多数基本 .NET 类型都可以自动处理,无需任何设置。

这是通过 ETL 系统中的两个关键设置实现的

  1. ChoJSONRecordConfiguration.CultureInfo - 代表特定文化的信息,包括文化名称、书写系统和使用的日历,以及访问文化特定对象,这些对象为常见操作(如格式化日期和排序字符串)提供信息。默认值为 'en-US'。
  2. ChoTypeConverterFormatSpec - 这是一个全局格式说明符类,包含所有固有的 .NET 类型格式规范。

在本节中,我将讨论如何根据解析需求更改每个 .NET 内在数据类型的默认格式规范。

ChoTypeConverterFormatSpec 是单例类,其实例通过 'Instance' static 成员公开。它是线程本地的,意味着每个线程都会保留一个单独的实例副本。

每个固有类型都提供了两组格式规范成员,一组用于加载,另一组用于写入值,但 BooleanEnumDataTime 类型除外。这些类型只有一组成员用于加载和写入操作。

通过 ChoTypeConverterFormatSpec 指定每个固有数据类型的格式规范将影响整个系统,即设置 ChoTypeConverterFormatSpec.IntNumberStyle = NumberStyles.AllowParentheses 将影响 JSON 对象的所有整数成员以允许使用括号。如果您想覆盖此行为并控制特定 JSON 数据成员以处理其来自全局系统范围设置的独特 JSON 值解析,可以通过在 JSON 字段成员级别指定 TypeConverter 来实现。有关更多信息,请参阅**第 13.4 节**。

NumberStyles(可选)用于从 JSON 流加载值,而格式字符串用于将值写入 JSON 流。

在本文中,我将简要介绍如何使用 NumberStyles 从流加载 JSON 数据。这些值是可选的。它确定了解析 JSON 文件时每种类型允许的样式。系统会自动确定如何解析和加载来自底层文化的值。在特殊情况下,您可能希望覆盖并按照您想要的方式设置样式,以成功加载文件。有关 NumberStyles 及其值的更多信息,请参阅 MSDN NumberStyles

清单 20.1.1 ChoTypeConverterFormatSpec 成员
public class ChoTypeConverterFormatSpec
{
    public static readonly ThreadLocal<ChoTypeConverterFormatSpec> Instance = 
    new ThreadLocal<ChoTypeConverterFormatSpec>(() => new ChoTypeConverterFormatSpec());

    public string DateTimeFormat { get; set; }
    public ChoBooleanFormatSpec BooleanFormat { get; set; }
    public ChoEnumFormatSpec EnumFormat { get; set; }

    public NumberStyles? CurrencyNumberStyle { get; set; }
    public string CurrencyFormat { get; set; }

    public NumberStyles? BigIntegerNumberStyle { get; set; }
    public string BigIntegerFormat { get; set; }

    public NumberStyles? ByteNumberStyle { get; set; }
    public string ByteFormat { get; set; }

    public NumberStyles? SByteNumberStyle { get; set; }
    public string SByteFormat { get; set; }

    public NumberStyles? DecimalNumberStyle { get; set; }
    public string DecimalFormat { get; set; }

    public NumberStyles? DoubleNumberStyle { get; set; }
    public string DoubleFormat { get; set; }

    public NumberStyles? FloatNumberStyle { get; set; }
    public string FloatFormat { get; set; }

    public string IntFormat { get; set; }
    public NumberStyles? IntNumberStyle { get; set; }

    public string UIntFormat { get; set; }
    public NumberStyles? UIntNumberStyle { get; set; }

    public NumberStyles? LongNumberStyle { get; set; }
    public string LongFormat { get; set; }

    public NumberStyles? ULongNumberStyle { get; set; }
    public string ULongFormat { get; set; }

    public NumberStyles? ShortNumberStyle { get; set; }
    public string ShortFormat { get; set; }

    public NumberStyles? UShortNumberStyle { get; set; }
    public string UShortFormat { get; set; }
}

下面的示例展示了如何使用 ChoJSONReader 加载包含 'se-SE'(瑞典语)文化特定数据的 JSON 数据流。此外,输入源中的 'EmployeeNo' 值包含括号。为了成功加载,我们需要将 ChoTypeConverterFormatSpec.IntNumberStyle 设置为 NumberStyles.AllowParenthesis

清单 20.1.2 在代码中使用 ChoTypeConverterFormatSpec
static void UsingFormatSpecs()
{
    ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
    config.Culture = new System.Globalization.CultureInfo("se-SE");
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Name"));
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Salary") { FieldType = typeof(ChoCurrency) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("JoinedDate") { FieldType = typeof(DateTime) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("EmployeeNo") { FieldType = typeof(int) });

    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;

    using (var parser = new ChoJSONReader("emp.json", config))
    {
        object row = null;

        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

20.2 货币支持

Cinchoo ETL 提供 ChoCurrency 对象来读写 JSON 文件中的货币值。ChoCurrency 是一个包装类,用于以 decimal 类型存储货币值,并支持在 JSON 加载期间以文本格式序列化它们。

清单 20.2.1 在动态模型中使用 Currency 成员
static void CurrencyDynamicTest()
{
    ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Id"));
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Name"));
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Salary") { FieldType = typeof(ChoCurrency) });

    using (var parser = new ChoJSONReader("emp.json", config))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

上面的示例展示了如何在动态对象模型中使用货币值。默认情况下,动态对象的所有成员都被视为 string 类型,除非通过 ChoJSONFieldConfiguration.FieldType 显式指定。通过将 'Salary' JSON 字段的字段类型指定为 ChoCurrencyChoJSONReader 会将它们加载为货币对象。

附注ChoJSONReader 通过 ChoRecordConfiguration.CultureChoTypeConverterFormatSpec.CurrencyNumberStyle 来确定货币值的格式。

下面的示例展示了如何在 POCO 实体类中使用 ChoCurrency JSON 字段。

清单 20.2.2 在 POCO 模型中使用 Currency 成员
public class EmployeeRecWithCurrency
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ChoCurrency Salary { get; set; }
}

static void CurrencyTest()
{
    using (var parser = new ChoJSONReader<EmployeeRecWithCurrency>("emp.json"))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

20.3 枚举支持

Cinchoo ETL 会自动处理从 JSON 文件解析 enum 字段值。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.EnumFormat 全局指定它们。默认值为 ChoEnumFormatSpec.Value

FYI,更改此值将影响整个系统。

可以使用三个可能的值:

  1. ChoEnumFormatSpec.Value - Enum 值用于解析。
  2. ChoEnumFormatSpec.Name - Enum 键名用于解析。
  3. ChoEnumFormatSpec.Description - 如果每个 enum 键都用 DescriptionAttribute 修饰,则使用其值进行解析。
清单 20.3.1 在解析期间指定 Enum 格式规范
public enum EmployeeType
{
    [Description("Full Time Employee")]
    Permanent = 0,
    [Description("Temporary Employee")]
    Temporary = 1,
    [Description("Contract Employee")]
    Contract = 2
}

static void EnumTest()
{
    ChoTypeConverterFormatSpec.Instance.EnumFormat = ChoEnumFormatSpec.Description;

    ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Name"));
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Salary") { FieldType = typeof(ChoCurrency) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("JoinedDate") { FieldType = typeof(DateTime) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("EmployeeType") 
    { FieldType = typeof(EmployeeType) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;

    using (var parser = new ChoJSONReader("emp.json", config))
    {
        object row = null;

        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

20.4 布尔值支持

Cinchoo ETL 会自动处理从 JSON 文件解析布尔值 JSON 字段。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.BooleanFormat 全局指定它们。默认值为 ChoBooleanFormatSpec.ZeroOrOne

FYI,更改此值将影响整个系统。

有四种可能的值:

  1. ChoBooleanFormatSpec.ZeroOrOne - '0' 表示 false。'1' 表示 true
  2. ChoBooleanFormatSpec.YOrN - 'Y' 表示 true,'N' 表示 false
  3. ChoBooleanFormatSpec.TrueOrFalse - 'True' 表示 true,'False' 表示 false
  4. ChoBooleanFormatSpec.YesOrNo - 'Yes' 表示 true,'No' 表示 false
清单 20.4.1 在解析期间指定布尔值格式规范
static void BoolTest()
{
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.ZeroOrOne;

    ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Name"));
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Salary") { FieldType = typeof(ChoCurrency) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("JoinedDate") { FieldType = typeof(DateTime) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Active") { FieldType = typeof(bool) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;

    using (var parser = new ChoJSONReader("emp.json", config))
    {
        object row = null;

        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

20.5 DateTime 支持

Cinchoo ETL 会使用系统文化或自定义设置的文化,自动处理从 JSON 文件解析 datetime JSON 字段值。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.DateTimeFormat 全局指定它们。默认值为 'd'。

FYI,更改此值将影响整个系统。

您可以使用任何有效的 .NET 标准自定义日期时间格式规范来解析文件中的 datetime JSON 值。

清单 20.5.1 在解析期间指定 datetime 格式规范
static void DateTimeTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "MMM dd, yyyy";

    ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Name"));
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Salary") { FieldType = typeof(ChoCurrency) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("JoinedDate") { FieldType = typeof(DateTime) });
    config.JSONRecordFieldConfigurations.Add
    (new ChoJSONRecordFieldConfiguration("Active") { FieldType = typeof(bool) });

    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;

    using (var parser = new ChoJSONReader("emp.json", config))
    {
        object row = null;

        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

上面的示例展示了如何从 JSON 文件解析自定义 datetime JSON 值。

注意:由于 datetime 值包含 JSON 分隔符,因此它们用双引号括起来以通过解析。

21. Fluent API

ChoJSONReader 通过流畅的 API 方法公开了一些常用的配置参数。这将使 JSON 文件解析的编程更快。

21.1 使用 JSONPath

此 API 方法使用 JSONPath 表达式设置要使用 ChoJSONReader 加载的节点。

foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json").WithJSONPath("$", true))
    Console.WriteLine(e.ToString());

21.3 使用字段

此 API 方法指定要用于解析和加载的 JSON 节点(属性或元素)列表。JSON 节点中的其他字段将被丢弃。

foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json").WithFields("Id", "Name"))
    Console.WriteLine(e.ToString());

21.4 使用单个字段

此 API 方法用于添加具有 JSONPath、数据类型和其他参数的 JSON 节点。此方法在动态对象模型中很有用,通过为每个单独的 JSON 节点指定适当的 datatype

foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json").WithField
    ("Id", fieldType: typeof(int)))
    Console.WriteLine(e.ToString());

21.5 列数严格

此 API 方法用于设置 ChoJSONWriter,以便在读取 JSON 文件之前执行字段计数检查。

foreach (var e in new ChoJSONReader<EmployeeRec>("emp.json").ColumnCountStrict())
    Console.WriteLine(e.ToString());

21.8. NotifyAfter

此 API 方法用于定义在生成通知事件之前要处理的行数。此属性专为说明 JSON 加载进度的用户界面组件而设计。通知会发送给订阅 RowsLoaded 事件的订阅者。

static void NotifyAfterTest()
{
    using (var parser = new ChoJSONReader("emp.json")
        .NotifyAfter(1000)
        )
    {
        parser.RowsLoaded += (o, e) => Console.WriteLine(e.RowsLoaded);

        foreach (var rec in parser)
        {
            Console.WriteLine(String.Format("Id: {0}", rec.Id));
            Console.WriteLine(String.Format("Name: {0}", rec.Name));
            Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
        }
    }
}

21.9. Configure

此 API 方法用于配置未通过流畅 API 公开的所有配置参数。

static void ConfigureTest()
{
    using (var parser = new ChoJSONReader("emp.json")
        .Configure(c => c.ErrorMode = ChoErrorMode.ThrowAndStop)
        )
    {
        foreach (var rec in parser)
        {
            Console.WriteLine(String.Format("Id: {0}", rec.Id));
            Console.WriteLine(String.Format("Name: {0}", rec.Name));
            Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
        }
    }
}

21.10. Setup

此 API 方法用于通过流畅 API 设置读取器的参数/事件。

static void SetupTest()
{
    using (var parser = new ChoJSONReader("emp.json")
        .Setup(r => r.BeforeRecordLoad += (o, e) =>
        {
            if (e.Source.CastTo<JObject>().ContainsKey("Name1"))
                e.Skip = true;
        }
        )
    {
        foreach (var rec in parser)
        {
            Console.WriteLine(String.Format("Id: {0}", rec.Id));
            Console.WriteLine(String.Format("Name: {0}", rec.Name));
            Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
        }
    }
}

22. FAQ

22.1. 如何将字符串反序列化为枚举?

ChoJSONReader 会自动处理将 enum 文本转换为 enum 值。下面的示例展示了如何使用 POCO 对象加载 JSON。

public enum Gender { Male, Female }
public class Employee
{
    public int Age { get; set; }
    public Gender Gender { get; set; }
}

static void EnumTest()
{
    string json = @"{ ""Age"": 35, ""Gender"": ""Female"" }";

    using (var r = ChoJSONReader<Employee>.LoadText(json))
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

示例 Fiddle: https://dotnetfiddle.net/0E1YKl

下面的示例展示了如何在动态对象模型方法中解析包含 enum 值的 JSON。

static void DynamicEnumTest()
{
    string json = @"{ ""Age"": 35, ""Gender"": ""Male"" }";

    using (var r = ChoJSONReader.LoadText(json)
        .WithField("Age")
        .WithField("Gender", fieldType: typeof(Gender))
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

示例 Fiddle: https://dotnetfiddle.net/Rt2upb

22.2. 如何仅加载键值对 JSON 中的值?

使用自定义 JSONPath($..^),您可以仅从键值对 JSON 数据中加载值。

public class Balance
{
    public float amount { get; set; }
    public float value { get; set; }
}

static void LoadDictValuesTest()
{
    string json = @"{
""AE"": {
""amount"": ""0.00000000"",
""value"": ""0.00000000""
},
""AR"": {
""amount"": ""0.00000000"",
""value"": ""0.00000000""
},
""BC"": {
""amount"": ""0.09670332"",
""value"": ""3.74814004""
}
}";

    using (var r = ChoJSONReader< Balance>.LoadText(json)
        .WithJSONPath("$..^")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

示例 Fiddle: https://dotnetfiddle.net/yuWly8

同样,下面的示例展示了如何在动态方法中使用值加载。

public static void Main()
{
    string json = @"{
    ""AE"": {
        ""amount"": ""1.00000000"",
        ""value"": ""2.00000000""
    },
    ""AR"": {
        ""amount"": ""3.10000000"",
        ""value"": ""4.500000000""
    },
    ""BC"": {
        ""amount"": ""0.09670332"",
        ""value"": ""3.74814004""
    }
}";

    using (var r = ChoJSONReader.LoadText(json)
        .WithJSONPath("$..^")
        )
    {
        foreach (var rec in r)
            rec.Print();
    }
}

示例 Fiddle: https://dotnetfiddle.net/u0E5Mv

22.3. 如何仅加载键值对 JSON 中的键?

使用自定义 JSONPath($..~),您可以仅从 JSON 中加载值。

static void LoadDictKeysTest()
{
    string json = @"{
""AE"": {
""amount"": ""0.00000000"",
""value"": ""0.00000000""
},
""AR"": {
""amount"": ""0.00000000"",
""value"": ""0.00000000""
},
""BC"": {
""amount"": ""0.09670332"",
""value"": ""3.74814004""
}
}";

    using (var r = ChoJSONReader<string>.LoadText(json)
        .WithJSONPath("$..^")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

示例 Fiddle: https://dotnetfiddle.net/ziAzHc

22.4. 将 JSON 反序列化为动态对象?

ChoJSONReader 在动态对象模型中隐式执行此操作。

static void DynamicEnumTest()
{
    string json = @"{ ""Age"": 35, ""Gender"": ""Male"" }";

    using (dynamic r = ChoJSONReader.LoadText(json)
        .WithField("Age")
        .WithField("Gender", fieldType: typeof(Gender))
        )
    {
        foreach (var rec in r)
        {
            Console.WriteLine(rec.Age);
            Console.WriteLine(rec.Gender);
        }
    }
}

在上面,解析器加载 json 文件,构造并返回 dynamic 对象。

22.5. 如何将 JSON 转换为 XML?

Cinchoo ETL 提供 ChoXmlWriter 来从对象生成 XML 文件。通过 ChoJSONReaderChoXmlWriter,您可以轻松地将 JSON 转换为 XML 格式。

static void JSON2XmlTest()
{
    string json = @"[
  {
    ""Id"": 1,
    ""Name"": ""Mark""
  },
  {
    ""Id"": 2,
    ""Name"": ""Tom""
  }
]
";
    StringBuilder xml = new StringBuilder();
    using (var r = ChoJSONReader.LoadText(json))
    {
        using (var w = new ChoXmlWriter(xml)
            .WithRootName("Emps")
            .WithNodeName("Emp")
            )
            w.Write(r);
    }
    Console.WriteLine(xml.ToString());
}

输出

<Emps>
  <Emp>
    <Id>1</Id>
    <Name>Mark</Name>
  </Emp>
  <Emp>
    <Id>2</Id>
    <Name>Tom</Name>
  </Emp>
</Emps>

示例 Fiddle: https://dotnetfiddle.net/dgvc1D

22.6. 如何仅加载 JSON 中的部分节点?

使用 JSONPath,您可以使用 ChoJSONReader 将 JSON 中的部分节点加载到对象中。

对于下面的示例 JSON:

{
    "user": {
        "name": "asdf",
        "teamname": "b",
        "email": "c",
        "players": ["1", "2"]
    }
}

想要将 User 数据加载到对象中。示例代码使用 JSONPath "$.user" 来选择节点并对其进行解析。

public class UserInfo
{
    public string name { get; set; }
    public string teamname { get; set; }
    public string email { get; set; }
    public string[] players { get; set; }
}

static void ReadSelectNodeTest()
{
    string json = @"
{
    ""user"": {
        ""name"": ""asdf"",
        ""teamname"": ""b"",
        ""email"": ""c"",
        ""players"": [""1"", ""2""]
    }
}";

    using (var r = ChoJSONReader<UserInfo>.LoadText(json)
        .WithJSONPath("$.user")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

示例 Fiddle: https://dotnetfiddle.net/wyZWpG

22.7. 如何仅加载 JSON 中的部分子节点?

通过每个成员级别的 JSONPath,您可以使用 ChoJSONReader 将 JSON 中的部分子节点加载到对象成员中。

对于下面的示例 JSON:

{
    "user": {
        "name": "asdf",
        "teamname": "b",
        "email": "c",
        "players": ["1", "2"]
    }
}

想要将 User 数据加载到对象中。示例代码在每个字段上使用 JSONPath 来选择子节点并对其进行解析。

public class UserInfo
{
    [ChoJSONRecordField(JSONPath = "$.name")]
    public string name { get; set; }
    [ChoJSONRecordField(JSONPath = "$.teamname")]
    public string teamname { get; set; }
    [ChoJSONRecordField(JSONPath = "$.email")]
    public string email { get; set; }
    [ChoJSONRecordField(JSONPath = "$.players[1]")]
    public string player { get; set; }
}

在上面,每个成员都指定了 JSONPath 来从 JSON 中选择节点值。这样,您就可以使用复杂的 JSONPath 来选择和提取 JSON 节点值,从而提供更多控制来选择数据。

static void ReadSelectNodeTest()
{
    string json = @"
{
    ""user"": {
        ""name"": ""asdf"",
        ""teamname"": ""b"",
        ""email"": ""c"",
        ""players"": [""1"", ""2""]
    }
}";

    using (var r = ChoJSONReader<UserInfo>.LoadText(json)
        .WithJSONPath("$.user")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

示例 Fiddle: https://dotnetfiddle.net/6jq9uv

22.8. 如何将 JSON 转换为 DataTable?

ChoJSONReader 提供了一个小辅助方法来将 JSON 转换为 Datatable,即 AsDataTable()

static void ConvertToDataTableTest()
{
    string json = @"
{
    ""user"": {
        ""name"": ""asdf"",
        ""teamname"": ""b"",
        ""email"": ""c"",
        ""players"": [""1"", ""2""]
    }
}";

    using (var r = ChoJSONReader<UserInfo>.LoadText(json)
        .WithJSONPath("$.user")
        .Configure(c => c.ArrayValueNamePrefix = String.Empty)
        )
    {
        var dt = r.AsDataTable();
    }
}

示例 Fiddle: https://dotnetfiddle.net/hdOdvi

22.9. 如何将 JSON 转换为 DataReader?

ChoJSONReader 提供了一个小辅助方法来将 JSON 转换为 DataReader,即 AsDataReader()

static void ConvertToDataTableTest()
{
    string json = @"
{
    ""user"": {
        ""name"": ""asdf"",
        ""teamname"": ""b"",
        ""email"": ""c"",
        ""players"": [""1"", ""2""]
    }
}";

    using (var r = ChoJSONReader<UserInfo>.LoadText(json)
        .WithJSONPath("$.user")
        )
    {
        var dt = r.AsDataReader();
    }
}

示例 Fiddle: https://dotnetfiddle.net/sCifj5

22.10. 如何将 JSON 转换为 CSV?

Cinchoo ETL 提供 ChoCSVWriter 来从对象生成 CSV 文件。通过 ChoJSONReaderChoCSVWriter,您可以轻松地将 JSON 转换为 CSV 格式。

JSON 是一个分层对象模型,CSV 是一个扁平结构。Cinchoo 无缝地处理它们并产生预期的输出。

static void Json2CSV2()
{
    string json = @" {id: 1, name: ""Tom"", friends: [""Dick"", ""Harry""]}";

    StringBuilder csv = new StringBuilder();
    using (var r = ChoJSONReader.LoadText(json)
        )
    {
        using (var w = new ChoCSVWriter(csv)
            .WithFirstLineHeader()
            .NestedColumnSeparator('/')
            )
            w.Write(r);
    }

    Console.WriteLine(csv.ToString());
}

输出

id,name,friends/0,friends/1
1,Tom,Dick,Harry

示例 Fiddle: https://dotnetfiddle.net/2xrf62

22.11. 如何反序列化对象?

此示例展示了如何将 JSON 反序列化为对象。

public class Account
{
    public string Email { get; set; }
    public bool Active { get; set; }
    public DateTime CreatedDate { get; set; }
    public IList<string> Roles { get; set; }
}
static void DeserializeObject()
{
    string json = @"{
'Email': 'james@example.com',
'Active': true,
'CreatedDate': '2013-01-20T00:00:00Z',
'Roles': [
'User',
'Admin'
]
}";

    Account account = ChoJSONReader.DeserializeText<Account>(json).FirstOrDefault();

    Console.WriteLine(account.Email);
}

示例 Fiddle: https://dotnetfiddle.net/CmeAfv

22.12. 如何在不使用 TypeNameHandling 的情况下将 JSON 反序列化为相关对象?

此示例展示了如何在不使用 TypeNameHandling 选项的情况下将 JSON 反序列化为继承层次结构中的对象。

public abstract class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Employee : Person
{
    public string Department { get; set; }
    public string JobTitle { get; set; }
}

public class Artist : Person
{
    public string Skill { get; set; }
}

JSON

[
  {
    "Department": "Department1",
    "JobTitle": "JobTitle1",
    "FirstName": "FirstName1",
    "LastName": "LastName1"
  },
  {
    "Department": "Department2",
    "JobTitle": "JobTitle2",
    "FirstName": "FirstName2",
    "LastName": "LastName2"
  },
  {
    "Skill": "Painter",
    "FirstName": "FirstName3",
    "LastName": "LastName3"
  }
]

代码

static void DeserializeDifferentObjects()
{
    using (var r = new ChoJSONReader<Person>("emp.json")
        .WithCustomRecordSelector(o =>
        {
            var pair = (Tuple<long, JObject>)o;
            var obj = pair.Item2;

            if (obj.ContainsKey("Skill"))
                return typeof(Artist);

            return typeof(Employee);
        })
        )
    {
        foreach (var rec in r)
        {
            Console.WriteLine(rec.Dump());
        }
    }
}

示例 Fiddle: https://dotnetfiddle.net/C8HQGS

22.13. 如何使用 TypeNameHandling 将 JSON 反序列化为相关对象?

此示例展示了如何使用 TypeNameHandling 选项将 JSON 反序列化为继承层次结构中的对象。

static void InterfaceTest()
{
    string json = @"
{
    "$type": "ChoJSONReaderTest.Program+Person1, ChoJSONReaderTest, 
     Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
    "Profession": {
        "$type": "ChoJSONReaderTest.Program+Programming, ChoJSONReaderTest, 
         Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
        "JobTitle": "Software Developer",
        "FavoriteLanguage": "C#"
    }
}";

    foreach (var rec in ChoJSONReader<Person1>.LoadText(json)
        .Configure(c => c.UseJSONSerialization = true)
        .Configure(c => c.JsonSerializerSettings.TypeNameHandling = TypeNameHandling.All)
        )
        Console.WriteLine(rec.Dump());
}

22.14. 如何反序列化集合?

此示例将 JSON 反序列化为集合。

static void DeserializeCollection()
{
    string json = @"['Starcraft','Halo','Legend of Zelda']";

    List<string> videogames = ChoJSONReader.DeserializeText<string>(json).ToList();

    Console.WriteLine(string.Join(", ", videogames.ToArray()));
}

22.15. 如何反序列化字典?

此示例将 JSON 反序列化为 Dictionary

static void DeserializeDictionary()
{
    string json = @"{
'href': '/account/login.aspx',
'target': '_blank'
}";

    Dictionary<string, string> htmlAttributes = 
    ChoJSONReader.DeserializeText<Dictionary<string, string>>(json).FirstOrDefault();

    Console.WriteLine(htmlAttributes["href"]);
    Console.WriteLine(htmlAttributes["target"]);
}

22.16. 如何从文件反序列化?

此示例将 JSON 反序列化为 Dictionary

public class Movie
{
    public string Name { get; set; }
    public int Year { get; set; }
}

static void DeserializeFromFile()
{
    Movie movie1 = ChoJSONReader.Deserialize<Movie>("movie.json").FirstOrDefault();
}

22.17. 如何使用自定义工厂进行反序列化?

此示例使用自定义工厂反序列化 JSON,以实例化 Person 类型的 Employee 实例。

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

public class Employee : Person
{
    public string Department { get; set; }
    public string JobTitle { get; set; }
}

static void CustomCreationTest()
{
    string json = @"{
'Department': 'Furniture',
'JobTitle': 'Carpenter',
'FirstName': 'John',
'LastName': 'Joinery',
'BirthDate': '1983-02-02T00:00:00'
}";

    ChoActivator.Factory = (type, args) =>
    {
        if (type == typeof(Person))
            return new Employee();
        else
            return null;
    };
    Person person = ChoJSONReader.DeserializeText<Person>(json).FirstOrDefault();
    Console.WriteLine(person.GetType().Name);
}

22.18. 如何使用 ChoJSONReader 处理同一属性是单个项目和数组的情况?

假设您有如下 JSON:

[
  {
    "email": "john.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": [
      "newuser",
      "transactional"
    ],
    "event": "open"
  },
  {
    "email": "jane.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": "olduser",
    "event": "open"
  }
]

在这里,您会注意到每个项目的 category 属性可以是简单的 string 或字符串数组。ChoJSONReader 无缝处理这种情况,并在不使用自定义转换器的情况下正确加载它们。

定义如下 POCO 类:

public class Item
{
    [JsonProperty("email")]
    public string Email { get; set; }

    [JsonProperty("timestamp")]
    public int Timestamp { get; set; }

    [JsonProperty("event")]
    public string Event { get; set; }

    [JsonProperty("category")]
    public List<string> Categories { get; set; }
}

以下是如何解析 JSON:

public static void SingleOrArrayItemTest()
{
    foreach (var rec in new ChoJSONReader<Item>("items.json"))
    {
        Console.WriteLine(rec.Dump());
    }
}

22.19. 在使用 ChoJSONReader 进行序列化时,如何指定自定义 DateTime 格式?

ChoJSONReader 可以使用当前系统文化自动转换 datetime 值。如果 JSON 包含自定义 datetime 格式的值,您可以设置自定义 datetime 格式以成功解析 JSON。

带有自定义 datetime 格式值的示例 JSON:

{
  'Department': 'Furniture',
  'JobTitle': 'Carpenter',
  'FirstName': 'John',
  'LastName': 'Joinery',
  'BirthDate': '30-12-2003'
}

定义如下 POCO 类以处理自定义 datetime 格式:

public class Employee
{
    public string Department { get; set; }
    public string JobTitle { get; set; }
    [DisplayFormat(DataFormatString = "dd-MM-yyyy")]
    public DateTime BirthDate { get; set; }
}

public class Employee
{
    [ChoJSONRecordField]
    public string Department { get; set; }
    [ChoJSONRecordField]
    public string JobTitle { get; set; }
    [ChoJSONRecordField(FormatText = "dd-MM-yyyy")]
    public DateTime BirthDate { get; set; }
}

如下使用解析器加载 JSON:

using (var r = ChoJSONReader<Employee>.LoadText(json))
{
    foreach (var rec in r)
        Console.WriteLine(rec.Dump());
}

在动态模型中,您可以如下设置自定义 datetime 格式:

using (var r = ChoJSONReader.LoadText(json)
    .WithField("Department")
    .WithField("JobTitle")
    .WithField("BirthDate", fieldType: typeof(DateTime), formatText: "dd-MM-yyyy")
    )
{
    foreach (var rec in r)
        Console.WriteLine(rec.Dump());
}

22.20. 如何为枚举类反序列化 JSON 属性?

本节介绍如何在反序列化 JSON 时使用 Enumeration 类(使用 ChoJSONReader)。对于那些想知道什么是 Enumeration 类的人,请访问此链接

有两种方法可以成功处理反序列化:

方法 1:使用隐式/显式运算符重载

public class CardType : Enumeration
{
    public static readonly CardType Amex = new CardType(1, "Amex");
    public static readonly CardType Visa = new CardType(2, "Visa");
    public static readonly CardType MasterCard = new CardType(3, "MasterCard");

    public CardType(int id, string name)
        : base(id, name)
    {
    }
    public static explicit operator CardType(string name)
    {
        if (name == "Amex")
            return Amex;
        if (name == "MasterCard")
            return MasterCard;
        else
            return Visa;
    }
}

public class Dto
{
    public string Name { get; set; }
    public CardType CardType { get; set; }
}

static void DeserializeEnumClass()
{
    string json = @"[
{
    ""Name"": ""Tom"",
    ""CardType"": ""Amex""
}
]";
    var x = ChoJSONReader.DeserializeText<Dto>(json).FirstOrDefault();
    Console.WriteLine(x.Dump());
}

方法 2:使用值转换器

public class CardType : Enumeration
{
    public static readonly CardType Amex = new CardType(1, "Amex");
    public static readonly CardType Visa = new CardType(2, "Visa");
    public static readonly CardType MasterCard = new CardType(3, "MasterCard");

    public CardType(int id, string name)
        : base(id, name)
    {
    }
}

public class CardTypeConverter : IValueConverter
{
    public object Convert
    (object value, Type targetType, object parameter, CultureInfo culture)
    {
        var name = value as string;
        if (name == "Amex")
            return CardType.Amex;
        if (name == "MasterCard")
            return CardType.MasterCard;
        else
            return CardType.Visa;
    }

    public object ConvertBack
    (object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class Dto
{
    public string Name { get; set; }
    [ChoTypeConverter(typeof(CardTypeConverter))]
    public CardType CardType { get; set; }
}

static void DeserializeEnumClass()
{
    string json = @"[
{
    ""Name"": ""Tom"",
    ""CardType"": ""Amex""
}
]";
    var x = ChoJSONReader.DeserializeText<Dto>(json).FirstOrDefault();
    Console.WriteLine(x.Dump());
}

22.21. 如何使用 ChoJSONReader 有条件地反序列化 List<T>?

假设您有如下 JSON,您只想反序列化 id > 0 的详细信息

{
  "id": 5,
  "name": "test",
  "details": [
    {
      "id": 12,
      "data1": 0.25
    },
    {
      "id": 0,
      "data1": 0.0
    }
  ]
}

首先,定义与上述 JSON 匹配的对象模型:

public class CTest
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Class2> Details { get; set; }
}

public class Class2
{
    public int Id { get; set; }
    public int Data1 { get; set; }
}

这是反序列化 id > 0 的详细信息的方法:

string json = @"[
{
  ""id"":5,
  ""name"":""test"",
  ""details"":[
    {
      ""id"":12,
      ""data1"":0.25
    },
    {
      ""id"":0,
      ""data1"":0.0
    },
  ]
}
]";
using (var r = ChoJSONReader<CTest>.LoadText(json)
    .RegisterNodeConverterForType<List<Class2>>(o =>
    {
        dynamic x = o as dynamic;
        var list = new List<Class2>();

        while (x.reader.Read() && x.reader.TokenType != JsonToken.EndArray)
        {
            if (x.reader.TokenType == JsonToken.StartObject)
            {
                var item = x.serializer.Deserialize<Class2>(x.reader);
                if (item.Id != 0)
                    list.Add(item);
            }
        }
        return list;
    })
)
{
    foreach (var rec in r)
        Console.WriteLine(rec.Dump());
}

23. 历史记录

  • 2020 年 5 月 22 日:初始版本
© . All rights reserved.