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

Cinchoo ETL - JSON 写入器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2020年6月9日

CPOL

27分钟阅读

viewsIcon

22113

.NET 的简单 JSON 写入器

目录

  1. 引言
  2. 要求
  3. “Hello World!”示例
    1. 快速写入 - 数据优先方法
    2. 代码优先方法
    3. 配置优先方法
  4. 写入所有记录
  5. 手动写入记录
  6. 自定义 JSON 记录
  7. 自定义 JSON 字段
    1. 默认值
    2. ChoFallbackValue
    3. 类型转换器
      1. 声明式方法
      2. 配置方法
      3. 自定义值转换器方法
    4. 验证
    5. ChoIgnoreMember
    6. StringLength
    7. 显示
    8. DisplayName
  8. 回调机制
    1. 使用 JSONWriter 事件
    2. 实现 IChoNotifyRecordWrite 接口
    3. BeginWrite
    4. EndWrite
    5. BeforeRecordWrite
    6. AfterRecordWrite
    7. RecordWriteError
    8. BeforeRecordFieldWrite
    9. AfterRecordFieldWrite
    10. RecordWriteFieldError
  9. 自定义
  10. 使用动态对象
  11. 异常
  12. 使用 MetadataType 注解
  13. 配置选项
    1. 手动配置
    2. 自动映射配置
    3. 附加 MetadataType 类
  14. ToTextAll 辅助方法
    1. ToText 辅助方法
  15. Writing DataReader 辅助方法
  16. Writing DataTable 辅助方法
  17. 高级主题 (Advanced Topics)
    1. 覆盖转换器格式规范
    2. 货币支持
    3. 枚举支持
    4. 布尔值支持
    5. 日期时间支持
  18. Fluent API
    1. NullValueHandling
    2. 格式化
    3. WithFields
    4. WithField
    5. IgnoreFieldValueMode
    6. ColumnCountStrict
    7. 配置
    8. 安装
  19. 常见问题解答
    1. 如何序列化一个对象?
    2. 如何序列化对象集合?
    3. 如何序列化动态对象?
    4. 如何序列化匿名对象?
    5. 如何序列化集合?
    6. 如何序列化字典?
    7. 如何序列化 DataTable?
    8. 如何将 JSON 序列化到文件?
    9. 如何序列化非缩进 JSON?
    10. 如何序列化条件属性?
    11. 如何在自定义日期格式中序列化日期时间?
    12. 如何从 JSON 序列化中排除属性?
    13. 如何将 XML 转换为 JSON?
    14. 如何将 CSV 转换为 JSON?
  20. 历史

1. 引言

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

本文将介绍 ChoETL 框架提供的 ChoJSONWriter 组件。它是一个简单的实用类,用于将 JSON 数据保存到文件/外部数据源。

相应的 ChoJSONReader(JSON 读取器)文章可以在 这里找到。

特点

  • 遵循 JSON 标准文件规则
  • 在生成文件时支持特定文化的日期、货币和数字格式
  • 支持不同的字符编码
  • 在写入文件时提供对日期、货币、枚举、布尔值、数字格式的精细控制
  • 详细且强大的错误处理,让您能够快速查找和修复问题
  • 缩短您的开发时间

2. 要求

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

3. “Hello World!” 示例

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

让我们从一个生成下面具有两列的 JSON 文件的简单示例开始。

列表 3.1 示例 JSON 数据文件(emp.json)
[
  {
    "Id": 1,
    "Name": "Mark"
  },
  {
    "Id": 2,
    "Name": "Jason"
  }
]

有多种方法可以以最少的设置创建 JSON 文件。

3.1. 快速写入 - 数据优先方法

这是 **零配置** 且最快捷的方法,可以立即创建 JSON 文件。不需要类型化的 POCO 对象。下面的示例代码演示了如何使用动态对象生成示例 JSON 文件。

列表 3.1.1 将对象列表写入 JSON 文件
List<ExpandoObject> objs = new List<ExpandoObject>();
dynamic rec1 = new ExpandoObject();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
dynamic rec2 = new ExpandoObject();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoJSONWriter("emp.json"))
{
    parser.Write(objs);
}

在上面的示例中,我们一次性将动态对象列表提供给 JSONWriter,以便将它们写入 JSON 文件。

列表 3.1.2 将每个对象写入 JSON 文件
using (var parser = new ChoJSONWriter("emp.json"))
{
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 1;
    rec1.Name = "Mark";
    parser.Write(item);

    dynamic rec1 = new ExpandoObject();
    rec1.Id = 2;
    rec1.Name = "Jason";
    parser.Write(item);
}

在上面的示例中,我们通过使用 `Write` 重载来控制构造并逐个将动态记录传递给 JSONWriter 来生成 JSON 文件。

3.2. 代码优先方法

这是另一种使用类型化 POCO 类生成 JSON 文件的 **零配置** 方法。首先,定义一个简单的 POCO 类来匹配底层 JSON 文件布局。

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

在上面,POCO 类定义了两个属性,与示例 JSON 文件模板匹配。

列表 3.2.2 保存到 JSON 文件
List<EmployeeRecSimple> objs = new List<EmployeeRecSimple>();

EmployeeRecSimple rec1 = new EmployeeRecSimple();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRecSimple rec2 = new EmployeeRecSimple();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoJSONWriter<EmployeeRecSimple>("emp.json"))
{
    parser.Write(objs);
}

上面的示例演示了如何从类型化 POCO 类对象创建 JSON 文件。

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 属性,与示例 JSON 文件模板匹配。

列表 3.3.2 无 POCO 对象生成 JSON 文件
List<ExpandoObject> objs = new List<ExpandoObject>();

dynamic rec1 = new ExpandoObject();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
dynamic rec2 = new ExpandoObject();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoJSONWriter("emp.json", config))
{
    parser.Write(objs);
}

上面的示例代码展示了如何使用预定义的 JSON 配置设置,从动态对象列表中生成 JSON 文件。在 JSONWriter 构造函数中,我们指定了 JSON 配置对象,以在创建文件时遵守 JSON 布局模式。如果 JSON 列的名称或数量有任何不匹配,它将被报告为错误并停止写入过程。

列表 3.3.3 使用 POCO 对象保存 JSON 文件
List<EmployeeRecSimple> objs = new List<EmployeeRecSimple>();

EmployeeRecSimple rec1 = new EmployeeRecSimple();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRecSimple rec2 = new EmployeeRecSimple();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoJSONWriter<EmployeeRecSimple>("emp.json", config))
{
    parser.Write(objs);
}

上面的示例代码展示了如何使用 JSON 配置对象从 POCO 对象列表中生成 JSON 文件。在 JSONWriter 构造函数中,我们指定了 JSON 配置对象。

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 对象以及生成 JSON 文件所需的必要属性。第一项为每个记录字段定义一个属性,并使用 ChoJSONRecordFieldAttribute 来限定 JSON 记录映射。`Id` 是一个必需的属性。我们使用 RequiredAttribute 对其进行了装饰。`Name` 使用 DefaultValueAttribute 指定了默认值。这意味着,如果对象中未设置 `Name` 值,JSONWriter 将将默认值 'XXXX' 写入文件。

这非常简单,并且可以立即保存 JSON 数据。

列表 3.4.2 使用 POCO 对象保存 JSON 文件
List<EmployeeRec> objs = new List<EmployeeRec>();

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRec rec2 = new EmployeeRec();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoJSONWriter<EmployeeRec>("emp.json"))
{
    parser.Write(objs);
}

我们首先创建一个 ChoJSONWriter 对象的新实例。仅此而已。生成 JSON 数据的繁重工作由写入器在后台完成。

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

4. 写入所有记录

它就像设置与 JSON 文件结构匹配的 POCO 对象,构造对象列表,然后将其传递给 JSONWriterWrite 方法一样简单。这将一次性将整个对象列表写入 JSON 文件。

列表 4.1 写入 JSON 文件
List<EmployeeRec> objs = new List<EmployeeRec>();
//Construct and attach objects to this list
...

using (var parser = new ChoJSONWriter<EmployeeRec>("emp.json"))
{
    parser.Write(objs);
}

列表 4.2 写入 JSON 文件流
List<EmployeeRec> objs = new List<EmployeeRec>();
//Construct and attach objects to this list
...

using (var tx = File.OpenWrite("emp.json"))
{
    using (var parser = new ChoJSONWriter<EmployeeRec>(tx))
    {
        parser.Write(objs);
    }
}

这种模型使您的代码优雅、整洁、易于阅读和维护。

5. 手动写入记录

当 POCO 对象以非连接方式构造时,这是写入每个单独记录到 JSON 文件的替代方法。

列表 5.1 写入 JSON 文件
var writer = new ChoJSONWriter<EmployeeRec>("emp.json");

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 1;
rec1.Name = "Mark";
 
writer.Write(rec1);

EmployeeRec rec2 = new EmployeeRec();
rec1.Id = 2;
rec1.Name = "Jason"; 

writer.Write(rec2);

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 加载操作的可用属性

  • Culture - 用于读取和写入的文化信息。
  • ColumnCountStrict - 此标志指示如果 JSON 字段配置与数据对象成员不匹配,是否应抛出异常。
  • NullValue - 特殊的 `null` 值文本,在记录级别期望将其作为 JSON 文件中的 `null` 值处理。
  • ErrorMode - 此标志指示在写入过程中是否应抛出异常,并且预期的字段未能写入。可以按属性覆盖。可能的值是
    • IgnoreAndContinue - 忽略错误,跳过记录并继续处理下一条。
    • ReportAndContinue - 如果 POCO 实体是 IChoNotifyRecordWrite 类型,则向其报告错误。
    • ThrowAndStop - 抛出错误并停止执行。
  • IgnoreFieldValueMode - 不适用
  • ObjectValidationMode - 一个标志,告知读取器关于记录对象要执行的验证类型。可能的值是
    • Off - 不执行对象验证。(默认)
    • MemberLevel - 在写入每个 JSON 属性到文件之前执行的验证
    • ObjectLevel - 在将所有 POCO 属性写入文件之前执行的验证

7. 自定义 JSON 字段

对于每个 JSON 列,您可以使用 ChoJSONRecordFieldAttribute 在 POCO 实体属性中指定映射。

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

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

  • FieldName - JSON 字段名。如果未指定,则将使用 POCO 对象属性名作为字段名。
  • Size - JSON 列值的大小
  • NullValue - 特殊的 `null` 值文本,在字段级别期望将其作为 JSON 文件中的 `null` 值处理。
  • ErrorMode - 此标志指示在写入过程中是否应抛出异常,并且预期的字段未能转换和写入。可能的值是
    • IgnoreAndContinue - 忽略错误,继续加载记录的其他属性。
    • ReportAndContinue - 如果 POCO 实体是 IChoRecord 类型,则向其报告错误。
    • ThrowAndStop - 抛出错误并停止执行。

7.1. 默认值

任何 POCO 实体属性都可以使用 System.ComponentModel.DefaultValueAttribute 指定默认值。当 JSON 值 `null`(由 IgnoreFieldValueMode 控制)时,将使用此值进行写入。

7.2. 后备值

任何 POCO 实体属性都可以使用 ChoETL.ChoFallbackValueAttribute 指定回退值。当属性写入 JSON 失败时,将使用此值。 `Fallback` 值仅在 `ErrorMode` 为 `IgnoreAndContinue` 或 `ReportAndContinue` 时设置。

7.3. 类型转换器

大多数原始类型会自动转换为字符串/文本并保存到 JSON 文件。如果 JSON 字段的值未自动转换为文本值,您可以指定自定义/.NET 内置转换器来将值转换为文本。这些可以是 `IValueConverter`、`IChoValueConverter` 或 `TypeConverter` 转换器。

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

  • 声明式方法
  • 配置方法

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)
    {
        int intValue = (int)value;
        return intValue.ToString("D4");
    }
}

在上面的示例中,我们定义了自定义的 `IntConverter` 类。并展示了如何格式化 'Id' JSON 属性(带前导零)。

7.3.2. 配置式方法

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

列表 7.3.2.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"));
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name1"));

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

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

7.3.3. 自定义值转换器方法

此方法允许使用 `Fluent` API 将值转换器附加到每个 JSON 成员。这是一种快速处理任何奇怪转换过程并避免创建值转换器类的快捷方式。

列表 7.3.3.1 POCO 类
public class EmployeeRec
{
    [ChoJSONRecordField]
    public int Id { get; set; }
    [ChoJSONRecordField(2, FieldName ="Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

使用 `Fluent` API,下面的示例展示了如何将值转换器附加到 `Id` 列。

列表 7.3.3.2 附加值转换器
using (var dr = new ChoJSONWriter<EmployeeRec>(@"Test.json")
    .WithField(c => c.Id, valueConverter: (v) => 
              ((int)value).ToString("C3", CultureInfo.CurrentCulture))
    )
{
    Console.WriteLine(rec);
}

7.4. 验证

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

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

在上面的示例中,我为 `Id` 属性使用了 `Range` 验证属性。为 `Name` 属性使用了 `Required` 验证属性。当 `Configuration.ObjectValidationMode` 设置为 `ChoObjectValidationMode.MemberLevel` 或 `ChoObjectValidationMode.ObjectLevel` 时,JSONWriter 在将数据保存到文件之前对其进行验证。

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

图 7.4.2 在 POCO 实体上手动验证
[ChoJSONRecordObject]
public partial class EmployeeRec : IChoValidatable
{
    [ChoJSONRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoJSONRecordField]
    [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;
    }
}

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

IChoValidatable 接口公开以下方法

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

7.5. ChoIgnoreMember

如果您想在 `OptOut` 模式下从 JSON 解析中忽略 POCO 类成员,请使用 ChoIgnoreMemberAttribute 对其进行装饰。下面的示例显示 `Title` 成员已从 JSON 加载过程中忽略。

列表 7.5.1 忽略成员
public class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

7.6. StringLength

在 `OptOut` 模式下,您可以使用 `System.ComponentModel.DataAnnotations.StringLengthAttribute` 指定 JSON 列的大小。

列表 7.6.1 指定 JSON 成员的大小
public class EmployeeRec
{
    public int Id { get; set; }
    [StringLength(25)]
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

7.6. Display

在 `OptOut` 模式下,您可以使用 `System.ComponentModel.DataAnnotations.DisplayAttribute` 指定映射到成员的 JSON 列的名称。

列表 7.6.1 指定 JSON 列的名称
public class EmployeeRec
{
    public int Id { get; set; }
    [Display(Name="FullName")]
    [StringLength(25)]
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

7.7. DisplayName

在 `OptOut` 模式下,您可以使用 `System.ComponentModel.DataAnnotations.DisplayNameAttribute` 指定映射到成员的 JSON 列的名称。

列表 7.7.1 指定 JSON 列的名称
public class EmployeeRec
{
    public int Id { get; set; }
    [Display(Name="FullName")]
    [StringLength(25)]
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

8. 回调机制

JSONWriter 开箱即用提供行业标准的 JSON 数据文件生成,可满足大多数需求。如果生成过程无法满足您的任何需求,您可以使用 JSONWriter 提供的回调机制来处理这种情况。为了参与回调机制,您可以使用以下模型之一

  • 使用 JSONWriter 通过 IChoWriter 接口公开的事件处理程序
  • 使 POCO 实体对象继承自 IChoNotifyRecordWrite / IChoNotifyFileWrite / IChoNotifyRecordFieldWrite 接口
  • 使 `DataAnnotation` 的 `MetadataType` 类型对象通过 IChoNotifyRecordWrite / IChoNotifyFileWrite / IChoNotifyRecordFieldWrite 接口继承。

为了参与回调机制,POCO 实体对象或 `DataAnnotation` 的 `MetadataType` 类型对象必须继承 IChoNotifyRecordWrite 接口。

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

IChoRecorder 公开了以下方法

  • BeginWrite - 在 JSON 文件写入开始时调用
  • EndWrite - 在 JSON 文件写入结束时调用
  • BeforeRecordWrite - 在写入 JSON 记录之前引发
  • AfterRecordWrite - 在写入 JSON 记录之后引发
  • RecordWriteError - 在写入 JSON 记录时发生错误时引发
  • BeforeRecordFieldWrite - 在写入 JSON 列值之前引发
  • AfterRecordFieldWrite - 在写入 JSON 列值之后引发
  • RecordFieldWriteError - 在写入 JSON 列值时发生错误时引发

IChoNotifyRecordWrite 公开了以下方法

  • BeforeRecordWrite - 在写入 JSON 记录之前引发
  • AfterRecordWrite - 在写入 JSON 记录之后引发
  • RecordWriteError - 在写入 JSON 记录时发生错误时引发

IChoNotifyFileWrite 公开了以下方法

  • BeginWrite - 在 JSON 文件写入开始时调用
  • EndWrite - 在 JSON 文件写入结束时调用

IChoNotifyRecordFieldWrite 公开了以下方法

  • BeforeRecordFieldWrite - 在写入 JSON 列值之前引发
  • AfterRecordFieldWrite - 在写入 JSON 列值之后引发
  • RecordFieldWriteError - 在写入 JSON 列值时发生错误时引发

IChoNotifyFileHeaderArrange 公开了以下方法

  • FileHeaderArrange - 在将 JSON 文件头写入文件之前引发,这是一个重新排列 JSON 列的机会

IChoNotifyFileHeaderWrite 公开了以下方法

  • FileHeaderWrite - 在将 JSON 文件头写入文件之前引发,这是一个自定义文件头的机会。

8.1 使用 JSONWriter 事件

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

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

列表 8.1.1 使用 JSONWriter 回调事件
static void IgnoreLineTest()
{
    using (var parser = new ChoJSONWriter("emp.json"))
    { 
        parser.BeforeRecordWrite += (o, e) =>
        {
            if (e.Source != null)
            {
                e.Skip = ((JObject)e.Source).Contains("name1");
            }
        };
        
        parser.Write(rec);
    }
}

同样,您也可以在 JSONWriter 中使用其他回调方法。

8.2 实现 IChoNotifyRecordWrite 接口

下面的示例展示了如何实现 IChoNotifyRecordWrite 接口以直接用于 POCO 类。

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

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

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

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

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

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

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

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

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

    public bool RecordWriteError(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 类。

列表 8.2.2 基于 MetaDataType 的回调机制实现
[ChoMetadataRefType(typeof(EmployeeRec))]
[ChoJSONRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordWrite
{
    [ChoJSONRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

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

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

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

    public bool RecordWriteError(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; }
}

8.3 BeginWrite

此回调在 JSON 文件写入 **开始时** 调用一次。`source` 是 JSON 文件的 `stream` 对象。在这里,您可以检查 `stream`,返回 `true` 继续 JSON 生成。返回 `false` 停止生成。

列表 8.3.1 BeginWrite 回调示例
public bool BeginWrite(object source)
{
    StreamReader sr = source as StreamReader;
    return true;
}

8.4 EndWrite

此回调在 JSON 文件生成 **结束时** 调用一次。`source` 是 JSON 文件的 stream 对象。在这里,您可以检查 `stream`,对 `stream` 执行任何后续步骤。

列表 8.4.1 EndWrite 回调示例
public void EndWrite(object source)
{
    StreamReader sr = source as StreamReader;
}

8.5 BeforeRecordWrite

此回调在 **每个 POCO 记录对象写入 JSON 文件之前** 调用。`target` 是 POCO 记录对象的实例。`index` 是文件中的行索引。`source` 是 JSON 记录行。在这里,您可以检查 POCO 对象,并在需要时生成 JSON 记录行。

提示:如果您想跳过记录而不写入,请将 source 设置为 null

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

列表 8.5.1 BeforeRecordWrite 回调示例
public bool BeforeRecordWrite(object target, int index, ref object source)
{
    return true;
}

8.6 AfterRecordWrite

此回调在 **每个 POCO 记录对象写入 JSON 文件之后** 调用。`target` 是 POCO 记录对象的实例。`index` 是文件中的行索引。`source` 是 JSON 记录行。在这里,您可以对记录行执行任何后续操作。

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

列表 8.6.1 AfterRecordWrite 回调示例
public bool AfterRecordWrite(object target, int index, object source)
{
    return true;
}

8.7 RecordWriteError

在写入 POCO 记录对象时遇到 **错误** 时调用此回调。`target` 是 POCO 记录对象的实例。`index` 是文件中的行索引。`source` 是 JSON 记录行。`ex` 是异常对象。在这里,您可以处理异常。此方法仅在 `Configuration.ErrorMode` 设置为 `ReportAndContinue` 时调用。

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

列表 8.7.1 RecordWriteError 回调示例
public bool RecordLoadError(object target, int index, object source, Exception ex)
{
    return true;
}

8.8 BeforeRecordFieldWrite

此回调在 **每个 JSON 记录列写入 JSON 文件之前** 调用。`target` 是 POCO 记录对象的实例。`index` 是文件中的行索引。`propName` 是 JSON 记录属性名。`value` 是 JSON 列值。在这里,您可以检查 JSON 记录属性值并执行任何自定义验证等。

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

列表 8.8.1 BeforeRecordFieldWrite 回调示例
public bool BeforeRecordFieldWrite
       (object target, int index, string propName, ref object value)
{
    return true;
}

8.9 AfterRecordFieldWrite

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

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

列表 8.9.1 AfterRecordFieldWrite 回调示例
public bool AfterRecordFieldWrite
       (object target, int index, string propName, object value)
{
    return true;
}

8.10 RecordWriteFieldError

在写入 JSON 记录列值时遇到 **错误** 时调用此回调。`target` 是 POCO 记录对象的实例。`index` 是文件中的行索引。`propName` 是 JSON 记录属性名。`value` 是 JSON 列值。`ex` 是异常对象。在这里,您可以处理异常。在 JSONWriter 执行以下两个步骤序列后调用此方法

  • JSONWriter 查找每个 JSON 属性的 `FallbackValue` 值。如果存在,则尝试使用它进行写入。
  • 如果 FallbackValue 值不存在且 Configuration.ErrorMode 指定为 ReportAndContinue,则将执行此回调。

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

列表 8.10.1 RecordFieldWriteError 回调示例
public bool RecordFieldWriteError
(object target, int index, string propName, object value, Exception ex)
{
    return true;
}

9. 自定义

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

列表 9.1 运行时自定义 JSONWriter
class Program
{
    static void Main(string[] args)
    {        
        List<ExpandoObject> objs = new List<ExpandoObject>();
        dynamic rec1 = new ExpandoObject();
        rec1.Id = 1;
        rec1.Name = "Mark";
        objs.Add(rec1);

        dynamic rec2 = new ExpandoObject();
        rec2.Id = 2;
        rec2.Name = "Jason";
        objs.Add(rec2);

        using (var parser = new ChoJSONWriter("emp.json"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

10. 使用动态对象

到目前为止,本文已介绍了如何将 JSONWriter 与 POCO 对象一起使用。JSONWriter 也支持在没有 POCO 实体对象的情况下生成 JSON 文件。它利用 .NET 的动态功能。下面的示例展示了如何使用动态对象生成 JSON 流。JSON 模式由第一个对象确定。如果动态对象的成员值存在不匹配,将引发错误并停止生成过程。

下面的示例展示了这一点

列表 10.1 从动态对象生成 JSON 文件
class Program
{
    static void Main(string[] args)
    {        
        List<ExpandoObject> objs = new List<ExpandoObject>();
        dynamic rec1 = new ExpandoObject();
        rec1.Id = 1;
        rec1.Name = "Mark";
        objs.Add(rec1);

        dynamic rec2 = new ExpandoObject();
        rec2.Id = 2;
        rec2.Name = "Jason";
        objs.Add(rec2);

        using (var parser = new ChoJSONWriter("emp.json"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

11. 异常

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

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

12. 使用 MetadataType 注解

Cinchoo ETL 与数据注解的 `MetadataType` 模型配合得更好。它是一种将 `MetaData` 类附加到数据模型类的方法。在此关联类中,您提供了数据模型中不存在的附加元数据信息。它的作用是在不修改类的情况下向类添加属性。您可以将此接受单个参数的属性添加到将包含所有属性的类中。当 POCO 类是由自动工具(例如 Entity Framework、MVC 等)自动生成时,这很有用。这就是第二个类发挥作用的原因。您可以添加新内容而不修改生成的文件。此外,这可以通过将关注点分离到多个类中来促进模块化。

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

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

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

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

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

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

    public bool RecordWriteError(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;
    }
}

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

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

13. 配置选项

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

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

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

列表 13.1 密封 POCO 实体类
public sealed class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

13.1 手动配置

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

列表 13.1.1 手动配置
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.ThrowAndStopOnMissingField = true;
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Id"));
config.JSONRecordFieldConfigurations.Add(new ChoJSONRecordFieldConfiguration("Name"));

13.2 自动映射配置

这是一个替代方法,并且是一种不太容易出错的方法,可以自动为 POCO 实体类映射 JSON 列。

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

列表 13.2.1 自动映射类
public class EmployeeRecMap
{
    [ChoJSONRecordField]
    public int Id { get; set; }
 
    [ChoJSONRecordField]
    public string Name { get; set; } 
}

然后,您可以使用它来自动映射 JSON 列,方法是使用 `ChoJSONRecordConfiguration.MapRecordFields` 方法。

列表 13.2.2 使用自动映射配置
ChoJSONRecordConfiguration config = new ChoJSONRecordConfiguration();
config.MapRecordFields<EmployeeRecMap>();

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 2;
rec1.Name = "Jason";

foreach (var e in new ChoJSONWriter<EmployeeRec>("emp.json", config)) 
    w.Write(rec1);

13.3 附加 MetadataType 类

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

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

列表 13.3.1 定义 MetadataType 类
[ChoJSONRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordWrite, IChoValidatable
{
    [ChoJSONRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    public int Id { get; set; }

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

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

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

    public bool RecordWriteError(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;
    }
}
列表 13.3.2 附加 MetadataType 类
//Attach metadata 
ChoMetadataObjectCache.Default.Attach<EmployeeRec>(new EmployeeRecMeta());

using (var tx = File.OpenWrite("emp.json"))
{
    using (var parser = new ChoJSONWriter<EmployeeRec>(tx))
    {
        parser.Write(objs);
    }
}

14. ToTextAll 辅助方法

这是一个非常方便的辅助方法,可以从对象列表中生成 JSON 格式的输出。它可以帮助您运行和测试不同的选项,以便在测试环境中快速查看 JSON 输出。

static void ToTextTest()
{
    List<EmployeeRec> objs = new List<EmployeeRec>();
    EmployeeRec rec1 = new EmployeeRec();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);
 
    EmployeeRec rec2 = new EmployeeRec();
    rec2.Id = 200;
    rec2.Name = "Lou";
    objs.Add(rec2);
 
    Console.WriteLine(ChoJSONWriter.ToTextAll(objs));
}

14a. ToText 辅助方法

这是一个非常方便的辅助方法,可以从单个对象生成 JSON 格式的输出。它可以帮助您运行和测试不同的选项,以便在测试环境中快速查看 JSON 输出。

static void ToTextTest()
{
    EmployeeRec rec1 = new EmployeeRec();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);
 
    Console.WriteLine(ChoJSONWriter.ToText(rec1));
}

15. 写入 DataReader 辅助方法

此辅助方法允许您从 ADO.NET `DataReader` 创建 JSON 文件/流。

static void WriteDataReaderTest()
{
    SqlConnection conn = new SqlConnection(connString);
    conn.Open();
    SqlCommand cmd = new SqlCommand("SELECT * FROM Members", conn);
    IDataReader dr = cmd.ExecuteReader();

    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json))
    {
        parser.Write(dr);
    }
    Console.WriteLine(json.ToString());
}

16. 写入 DataTable 辅助方法

此辅助方法允许您从 ADO.NET `DataTable` 创建 JSON 文件/流。

static void WriteDataTableTest()
{
    string connString = @"Data Source=(localdb)\v11.0;
                        Initial Catalog=TestDb;Integrated Security=True";

    SqlConnection conn = new SqlConnection(connString);
    conn.Open();
    SqlCommand cmd = new SqlCommand("SELECT * FROM Members", conn);
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataTable dt = new DataTable();
    da.Fill(dt);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        )
    {
        parser.Write(dt);
    }

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

17. 高级主题

17.1 覆盖转换器格式规范

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

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

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

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

ChoTypeConverterFormatSpec 是一个单例类,其实例通过 'Instance' 静态成员公开。它是线程本地的,这意味着每个线程将有一个单独的实例副本。

对于加载和写入值,每个固有类型都有两组格式规范成员,但布尔型、枚举型、日期时间型除外。这些类型只有用于加载和写入操作的一个成员。

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

列表 17.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; }
}

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

列表 17.1.2 在代码中使用 ChoTypeConverterFormatSpec
static void FormatSpecDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "d";
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.YOrN;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        )
    {
        parser.Write(objs);
    }

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

17.2 货币支持

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

列表 17.2.1 在动态模型中使用货币成员
static void CurrencyDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.CurrencyFormat = "C2";
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        )
    {
        parser.Write(objs);
    }

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

上面的示例展示了如何使用动态对象模型输出货币值。由于货币输出将包含千位分隔符,这将导致无法生成 JSON 文件。为了解决这个问题,我们指定写入器将所有字段加引号。

**附注**:货币值的格式由 JSONWriter 通过 ChoRecordConfiguration.CultureChoTypeConverterFormatSpec.CurrencyFormat 确定。

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

列表 17.2.2 在 POCO 模型中使用货币成员
public class EmployeeRecWithCurrency
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ChoCurrency Salary { get; set; }
}
 
static void CurrencyPOCOTest()
{
    List<EmployeeRecWithCurrency> objs = new List<EmployeeRecWithCurrency>();
    EmployeeRecWithCurrency rec1 = new EmployeeRecWithCurrency();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    EmployeeRecWithCurrency rec2 = new EmployeeRecWithCurrency();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter<EmployeeRecWithCurrency>(json)
        )
    {
        parser.Write(objs);
    }

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

17.3 枚举支持

Cinchoo ETL 隐式处理从 JSON 文件解析/写入 `enum` 列值。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.EnumFormat 全局指定。默认值为 ChoEnumFormatSpec.Value

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

可以使用三个可能的值:

  1. ChoEnumFormatSpec.Value - Enum 值用于解析。
  2. ChoEnumFormatSpec.Name - Enum 键名用于解析。
  3. ChoEnumFormatSpec.Description - 如果每个 enum 键都用 DescriptionAttribute 修饰,则使用其值进行解析。
列表 17.3.1 在解析期间指定枚举格式规范
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;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    rec1.Status = EmployeeType.Permanent;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    rec2.Status = EmployeeType.Contract;
    objs.Add(rec2);

    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        )
    {
        parser.Write(objs);
    }

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

17.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
列表 17.4.1 在解析期间指定布尔格式规范
static void BoolTest()
{
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.YOrN;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    rec1.Status = EmployeeType.Permanent;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    rec2.Status = EmployeeType.Contract;
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        )
    {
        parser.Write(objs);
    }

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

17.5 DateTime 支持

Cinchoo ETL 使用系统文化或自定义设置的文化隐式处理从 JSON 文件解析/写入 `datetime` JSON 列值。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.DateTimeFormat 全局指定。默认值为 'd'。

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

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

列表 17.5.1 在解析期间指定 datetime 格式规范
static void DateTimeDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "MMM dd, yyyy";
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        )
    {
        parser.Write(objs);
    }

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

上面的示例展示了如何将自定义 `datetime` 值生成到 JSON 文件。

**注意**:由于 `datetime` 值包含 JSON 分隔符,因此我们指示写入器将所有字段加引号。

18. Fluent API

JSONWriter 通过 Fluent API 方法公开了一些常用的配置参数。这将使生成 JSON 文件的编程更加快捷。

18.1. NullValueHandling

指定 ChoJSONWriter 的 `null` 值处理选项

  • Ignore - 写入 JSON 时忽略 `null` 值
  • Default - 写入 JSON 时包含 `null` 值

18.2. Formatting

指定 ChoJSONWriter 的格式化选项

  • None - 不应用特殊格式。这是默认设置。
  • Indented - 使子对象缩进。

18.3 WithFields

此 API 方法指定要用于写入 JSON 文件的 JSON 字段列表。其他字段将被丢弃。字段名不区分大小写。

static void QuickDynamicTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        .WithFields("Id", "Name")
        )
    {
        parser.Write(objs);
    }

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

18.4 WithField

此 API 方法用于添加具有特定数据类型、引用标志和/或引用字符的 JSON 列。此方法在动态对象模型中很有用,通过为每个 JSON 列指定适当的 `datatype`。

static void QuickDynamicTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        .WithField("Id", typeof(int))
        .WithField("Name"))
        )
    {
        parser.Write(objs);
    }

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

18.5. IgnoreFieldValueMode

指定 ChoJSONWriter 的忽略字段值

  • None - 忽略字段值已关闭。这是默认设置。
  • DbNull - 将忽略 `DBNull` 值。
  • Empty - 将忽略空文本值。
  • WhiteSpace - 将忽略空格文本。

18.6 ColumnCountStrict

此 API 方法用于在写入 JSON 文件之前设置 JSONWriter 来执行列计数一致性检查。

static void ColumnCountTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        .ColumnCountStrict()
        )
    {
        parser.Write(objs);
    }

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

18.7. Configure

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

static void ConfigureTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);

    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    objs.Add(rec2);

    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        .Configure(c => c.ErrorMode = ChoErrorMode.ThrowAndStop)
        )
    {
        parser.Write(objs);
    }

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

18.8. Setup

此 API 方法用于通过 `Fluent` API 设置写入器的参数/事件。

static void SetupTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);

    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    objs.Add(rec2);

    StringBuilder json = new StringBuilder();
    using (var parser = new ChoJSONWriter(json)
        .Setup(r => r.BeforeRecordWrite += (o, e) =>
        {
        })
        )
    {
        parser.Write(objs);
    }

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

19. FAQ

19.1. 如何序列化一个对象?

此示例将对象序列化为 JSON

string json = ChoJSONWriter.Serialize(new Account
{
    Email = "james@example.com",
    Active = true,
    Roles = new List<string>()
        {
            "DEV",
            "OPS"
        }
});

19.2. 如何序列化对象集合?

此示例将对象集合序列化为 JSON

string json = ChoJSONWriter.SerializeAll<Account>(new Account[] {
    new Account
    {
      Email = "james@example.com",
      Active = true,
      Roles = new List<string>()
      {
        "DEV",
        "OPS"
      }
    }
}
);

19.3. 如何序列化动态对象?

此示例将动态对象序列化为 JSON

dynamic obj = new ExpandoObject();
obj.Email = "james@example.com";
obj.Active = true;
obj.Roles = new List<string>()
{
    "DEV",
    "OPS"
};

string json = ChoJSONWriter.Serialize(obj);

Console.WriteLine(json);

19.4. 如何序列化匿名对象?

此示例将匿名对象序列化为 JSON

string json = ChoJSONWriter.Serialize(new
{
    Email = "james@example.com",
    Active = true,
    Roles = new List<string>()
    {
        "DEV",
        "OPS"
    }
});

Console.WriteLine(json);

19.5. 如何序列化集合?

此示例将集合序列化为 JSON

string json = ChoJSONWriter.SerializeAll(new int[] { 1, 2, 3 });

Console.WriteLine(json);

19.6. 如何序列化字典?

此示例将 `dictionary` 序列化为 JSON

string json = ChoJSONWriter.SerializeAll(new Dictionary<string, int>[] {
    new Dictionary<string, int>()
    {
        ["key1"] = 1,
        ["key2"] = 2
    }
    });

Console.WriteLine(json);

19.7. 如何序列化 DataTable?

此示例将 `datatable` 序列化为 JSON

StringBuilder sb = new StringBuilder();
string connectionstring = @"Data Source=(localdb)\MSSQLLocalDB;
                          Initial Catalog=Northwind;Integrated Security=True";
using (var conn = new SqlConnection(connectionstring))
{
    conn.Open();
    var comm = new SqlCommand("SELECT TOP 2 * FROM Customers", conn);
    SqlDataAdapter adap = new SqlDataAdapter(comm);

    DataTable dt = new DataTable("Customer");
    adap.Fill(dt);

    using (var parser = new ChoJSONWriter(sb)
        .Configure(c => c.IgnoreRootName = true)
        )
        parser.Write(dt);
}

Console.WriteLine(sb.ToString());

19.8. 如何将 JSON 序列化到文件?

此示例将 JSON 序列化到文件。

// serialize JSON to a string and then write string to a file
File.WriteAllText(@"c:\emp.json", ChoJSONWriter.Serialize(employee));

下面的示例展示了如何直接写入文件

using (StreamWriter file = File.CreateText(@"c:\emp.json"))
{
    using (var r = new ChoJSONWriter(file))
          r.Write(employee);
}

19.9. 如何序列化非缩进 JSON?

此示例将对象序列化为 JSON,不带任何格式化或缩进的空白。

string json = ChoJSONWriter.SerializeAll(new int[] { 1, 2, 3 },
    new ChoJSONRecordConfiguration().Configure(c => c.Formatting = Formatting.None));

Console.WriteLine(json);

19.10. 如何序列化条件属性?

此示例使用条件属性将属性排除在序列化之外。

public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }

    public bool ShouldSerializeManager()
    {
        // don't serialize the Manager property if an employee is their own manager
        return (Manager != this);
    }
}

public static void ConditionalPropertySerialize()
{
    Employee joe = new Employee();
    joe.Name = "Joe Employee";
    Employee mike = new Employee();
    mike.Name = "Mike Manager";

    joe.Manager = mike;

    // mike is his own manager
    // ShouldSerialize will skip this property
    mike.Manager = mike;

    string json = ChoJSONWriter.SerializeAll(new[] { joe, mike }, 
    new ChoJSONRecordConfiguration().Configure(c => c.UseJSONSerialization = true));

    Console.WriteLine(json);
}

19.11. 如何在自定义日期格式中序列化 datetime?

此示例使用 `DateFormatString` 设置来控制 `DateTime` 和 `DateTimeOffset` 的序列化方式。

IList<DateTime> dateList = new List<DateTime>
{
    new DateTime(2009, 12, 7, 23, 10, 0, DateTimeKind.Utc),
    new DateTime(2010, 1, 1, 9, 0, 0, DateTimeKind.Utc),
    new DateTime(2010, 2, 10, 10, 0, 0, DateTimeKind.Utc)
};

string json = ChoJSONWriter.SerializeAll(dateList, new JsonSerializerSettings
{
    DateFormatString = "d MMMM, yyyy",
    Formatting = Formatting.Indented
});

Console.WriteLine(json);

19.12. 如何从 JSON 序列化中排除属性?

这些示例展示了如何从 JSON 序列化中排除属性。

示例 1:使用 ChoIgnoreMemberAttribute

public class Account
{
    [ChoIgnoreMember]
    public string Email { get; set; }
    public bool Active { get; set; }
    public DateTime CreatedDate { get; set; }
    public IList<string> Roles { get; set; }
}

static void ExcludePropertyTest()
{
    string json = ChoJSONWriter.Serialize(new Account
    {
        Email = "james@example.com",
        Active = true,
        Roles = new List<string>()
            {
                "DEV",
                "OPS"
            }
    });

    Console.WriteLine(json);
}

示例 2:使用 JsonIgnoreAttribute

public class Account
{
    [JsonIgnore]
    public string Email { get; set; }
    public bool Active { get; set; }
    public DateTime CreatedDate { get; set; }
    public IList<string> Roles { get; set; }
}

static void ExcludePropertyTest()
{
    string json = ChoJSONWriter.Serialize(new Account
    {
        Email = "james@example.com",
        Active = true,
        Roles = new List<string>()
            {
                "DEV",
                "OPS"
            }
    });

    Console.WriteLine(json);
}

示例 3:在 ChoJSONConfiguration 上使用 IgnoreField

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 ExcludePropertyTest()
{
    string json = ChoJSONWriter.Serialize(new Account
    {
        Email = "james@example.com",
        Active = true,
        Roles = new List<string>()
            {
                "DEV",
                "OPS"
            }

    }, new ChoJSONRecordConfiguration<Account>().Ignore(f => f.Email));

    Console.WriteLine(json);
}

示例 4:在 ChoJSONWriter 上使用 IgnoreField

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 ExcludePropertyTest()
{
    StringBuilder json = new StringBuilder();
    using (var w = new ChoJSONWriter<Account>(json)
        .IgnoreField(f => f.Email)
        )
    {
        w.Write(new Account
        {
            Email = "james@example.com",
            Active = true,
            Roles = new List<string>()
            {
                "DEV",
                "OPS"
            }
        });
    }

    Console.WriteLine(json);
}

19.13. 如何将 XML 转换为 JSON?

此示例展示了如何将 XML 文件转换为 JSON。

string xml = @"<Employees xmlns=""http://company.com/schemas"">
    <Employee>
        <FirstName>name1</FirstName>
        <LastName>surname1</LastName>
    </Employee>
    <Employee>
        <FirstName>name2</FirstName>
        <LastName>surname2</LastName>
    </Employee>
    <Employee>
        <FirstName>name3</FirstName>
        <LastName>surname3</LastName>
    </Employee>
</Employees>";

StringBuilder json = new StringBuilder();
using (var r = ChoXmlReader.LoadText(xml))
{
    using (var w = new ChoJSONWriter(json))
        w.Write(r);
}

Console.WriteLine(json.ToString());

输出

[
  {
    "FirstName": "name1",
    "LastName": "surname1"
  },
  {
    "FirstName": "name2",
    "LastName": "surname2"
  },
  {
    "FirstName": "name3",
    "LastName": "surname3"
  }
]

19.14. 如何将 CSV 转换为 JSON?

此示例展示了如何将 CSV 文件转换为 JSON

string csv = @"Id, First Name
    1, Tom
    2, Mark";

StringBuilder json = new StringBuilder();
using (var r = ChoCSVReader.LoadText(csv)
    .WithFirstLineHeader()
    .WithMaxScanRows(2)
    )
{
    using (var w = new ChoJSONWriter(json))
    {
        w.Write(r.Take(2));
    }
}

Console.WriteLine(json.ToString());

20. 历史记录

  • 2020年10月9日:初始版本
© . All rights reserved.