Cinchoo ETL - JSON 读取器
.NET 简易 JSON 读取器
目录
- 1. 引言
- 2. 要求
- 3. “Hello World!” 示例
- 4. 读取所有记录
- 5. 手动读取记录
- 6. 自定义 JSON 记录
- 7. 自定义 JSON 字段
- 10. 回调机制
- 10. 定制
- 11. AsDataReader 辅助方法
- 12. AsDataTable 辅助方法
- 13. 使用动态对象
- 14. 处理已密封的 POCO 对象
- 15. 异常
- 17. 使用 MetadataType 注解
- 18. 配置选项
- 19. LoadText 辅助方法
- 20. 高级主题
- 21. Fluent API
- 22. FAQ
- 22.1. 如何将字符串反序列化为枚举?
- 22.2. 如何仅加载键值对 JSON 中的值?
- 22.3. 如何仅加载键值对 JSON 中的键?
- 22.4. 将 JSON 反序列化为动态对象?
- 22.5. 如何将 JSON 转换为 XML?
- 22.6. 如何仅加载 JSON 中的部分节点?
- 22.7. 如何仅加载 JSON 中的部分子节点?
- 22.8. 如何将 JSON 转换为 DataTable?
- 22.9. 如何将 JSON 转换为 DataReader?
- 22.10. 如何将 JSON 转换为 CSV?
- 22.11. 如何反序列化对象?
- 22.12. 如何在不使用 TypeNameHandling 的情况下将 JSON 反序列化为相关对象?
- 22.13. 如何使用 TypeNameHandling 将 JSON 反序列化为相关对象?
- 22.14. 如何反序列化集合?
- 22.15. 如何反序列化字典?
- 22.16. 如何从文件反序列化?
- 22.17. 如何使用自定义工厂进行反序列化?
- 22.18. 如何使用 ChoJSONReader 处理同一属性是单个项目和数组的情况?
- 22.19. 在使用 ChoJSONReader 进行序列化时,如何指定自定义 DateTime 格式?
- 22.20. 如何为枚举类反序列化 JSON 属性?
- 22.21. 如何使用 ChoJSONReader 有条件地反序列化 List<T>?
- 23. 历史记录
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/AEmpty
- 如果记录值为空,则跳过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 值未能设置时,用于并设置到属性的值。仅当 ErrorMode
为 IgnoreAndContinue
或 ReportAndContinue
时,才会设置 Fallback
值。
可以使用 ChoETL.ChoFallbackValueAttribute
为任何 POCO 实体属性指定回退值。
7.3. 类型转换器
大多数基本类型都会自动转换并设置到属性。如果 JSON 字段的值无法自动转换为属性的类型,您可以指定自定义/内置 .NET 转换器来转换该值。这些可以是 IValueConverter
或 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)
{
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.DataAnnotations
和 Validation 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.MemberLevel
或 ChoObjectValidationMode.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
接口 - 继承
DataAnnotation
的MetadataType
类型对象,并实现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
节点索引。source
是 JObject
节点。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 值未能设置时,用于并设置到属性的值。仅当 ErrorMode
为 IgnoreAndContinue
或 ReportAndContinue
时,才会设置 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 转换器来转换该值。这些可以是 IValueConverter
或 TypeConverter
转换器。
在动态对象模型中,您可以通过配置来指定这些转换器。请参阅下面的示例,了解为 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.DataAnnotations
和 Validation 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.MemberLevel
或 ChoObjectValidationMode.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 系统中的两个关键设置实现的
ChoJSONRecordConfiguration.CultureInfo
- 代表特定文化的信息,包括文化名称、书写系统和使用的日历,以及访问文化特定对象,这些对象为常见操作(如格式化日期和排序字符串)提供信息。默认值为 'en-US
'。ChoTypeConverterFormatSpec
- 这是一个全局格式说明符类,包含所有固有的 .NET 类型格式规范。
在本节中,我将讨论如何根据解析需求更改每个 .NET 内在数据类型的默认格式规范。
ChoTypeConverterFormatSpec
是单例类,其实例通过 'Instance
' static
成员公开。它是线程本地的,意味着每个线程都会保留一个单独的实例副本。
每个固有类型都提供了两组格式规范成员,一组用于加载,另一组用于写入值,但 Boolean
、Enum
、DataTime
类型除外。这些类型只有一组成员用于加载和写入操作。
通过 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 字段的字段类型指定为 ChoCurrency
,ChoJSONReader
会将它们加载为货币对象。
附注:ChoJSONReader
通过 ChoRecordConfiguration.Culture
和 ChoTypeConverterFormatSpec.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,更改此值将影响整个系统。
可以使用三个可能的值:
ChoEnumFormatSpec.Value
-Enum
值用于解析。ChoEnumFormatSpec.Name
-Enum
键名用于解析。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,更改此值将影响整个系统。
有四种可能的值:
ChoBooleanFormatSpec.ZeroOrOne
- '0
' 表示false
。'1
' 表示true
ChoBooleanFormatSpec.YOrN
- 'Y
' 表示true
,'N
' 表示false
ChoBooleanFormatSpec.TrueOrFalse
- 'True
' 表示true
,'False
' 表示false
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 文件。通过 ChoJSONReader
和 ChoXmlWriter
,您可以轻松地将 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 文件。通过 ChoJSONReader
和 ChoCSVWriter
,您可以轻松地将 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 日:初始版本