DataKit.Parser
1.0.0
See the version list below for details.
dotnet add package DataKit.Parser --version 1.0.0
NuGet\Install-Package DataKit.Parser -Version 1.0.0
<PackageReference Include="DataKit.Parser" Version="1.0.0" />
<PackageVersion Include="DataKit.Parser" Version="1.0.0" />
<PackageReference Include="DataKit.Parser" />
paket add DataKit.Parser --version 1.0.0
#r "nuget: DataKit.Parser, 1.0.0"
#:package DataKit.Parser@1.0.0
#addin nuget:?package=DataKit.Parser&version=1.0.0
#tool nuget:?package=DataKit.Parser&version=1.0.0
DataKit 数据解析类库 · 中文操作文档
DataKit 是一个 零依赖、面向 .NET 8/9 的轻量级 C# 类库,用来读取并查询 JSON / XML / TXT / CSV / INI 五种常见数据格式。它的核心设计理念是:
无论底层数据是哪种格式,解析后都会归一化成同一棵「数据树(
DataNode)」,你用完全相同的一套 API、同一套路径语法就能取任意格式里的值。
- 目标框架:
net9.0(同时兼容引用net8.0的项目) - 依赖:
System.Text.Json、System.Linq.XDocument,均为 .NET 自带,无需额外安装 NuGet 包 - 编译产物:
DataKit.dll(位于bin/Release/net9.0/)
目录
1. 安装与引用
方式一:项目引用(推荐,便于调试)
在你的 C# 项目(.csproj)里添加:
<ItemGroup>
<ProjectReference Include="..\DataKit\DataKit.csproj" />
</ItemGroup>
或在项目目录下执行:
dotnet add reference ..\DataKit\DataKit.csproj
方式二:DLL 引用
- 先生成类库:
dotnet build -c Release - 在
DataKit/bin/Release/net9.0/中找到DataKit.dll - 在你的项目里「添加引用 → 浏览 → 选择该 DLL」
注意:你的程序目标框架必须是 .NET 8 或 .NET 9(
net8.0/net9.0)。如果是老旧的 .NET Framework 4.x 项目,需要把类库的TargetFramework改为netstandard2.0后重新编译(功能会有小幅差异)。
在代码里引入命名空间
using DataKit; // DataParser / DataDocument / DataNode / TextFixer
2. 核心概念
库里只有三个你真正要打交道的核心类型:
| 类型 | 作用 |
|---|---|
DataParser |
静态入口。把文本/文件/流解析成 DataDocument。 |
DataDocument |
一份解析后的文档。包着根节点,提供所有「按路径取值」的便捷方法。 |
DataNode |
数据树上的一个节点(可以是一个值、一个对象,或一个数组)。 |
所有解析器(JSON/XML/TXT/CSV/INI)产出的都是 DataNode 树,因此你查询数据的方式与原始格式无关。
节点类型 NodeKind:
Value—— 标量(字符串、数字、布尔、null)Object—— 有名字的成员集合(如 JSON 对象、XML 元素)Array—— 有序的项集合(如 JSON 数组、CSV 多行)
3. 解析数据:入口 API
所有解析入口都在 DataParser 上,均为静态方法。
// 自动识别格式:以 { 或 [ 开头 → JSON;以 < 开头 → XML;其余 → 纯文本
DataDocument doc = DataParser.Parse(content);
// 显式指定格式
var d1 = DataParser.ParseJson(jsonText); // JSON
var d2 = DataParser.ParseXml(xmlText); // XML
var d3 = DataParser.ParseText(plainText); // 纯文本(见第 7.4 节约定)
var d4 = DataParser.ParseCsv(csvText); // CSV(首行作为表头)
var d5 = DataParser.ParseIni(iniText); // INI
// 解析文件(按扩展名判断格式)
var d6 = DataParser.ParseFile("config.json");
// 解析流(适合网络响应、嵌入资源等)
using var stream = File.OpenRead("data.xml");
var d7 = DataParser.ParseStream(stream);
解析文件时的格式判定
ParseFile(path) 按文件扩展名选择解析器:
| 扩展名 | 解析为 |
|---|---|
.json |
JSON |
.xml |
XML |
.txt |
纯文本 |
.csv |
CSV |
.ini |
INI |
| 其它 | 自动识别(同 Parse) |
ParseCsv 指定分隔符
// 默认逗号;分号分隔的 CSV 可以这样:
var d = DataParser.ParseCsv(csvText, delimiter: ';');
4. 路径语法
路径是取数据的核心。所有 Get* / TryGet* 方法都接收一个 path 字符串。
4.1 点号访问(对象成员)
doc.Get("user.address.city");
doc.Get("server.host");
4.2 方括号访问(数组索引)
doc.Get("items[0].name"); // 第一个 item 的 name
doc.Get("list[2]"); // 第三个元素
也支持用点 + 数字(等价写法):
doc.Get("items.0.name"); // 等同 items[0].name
4.3 谓词过滤(按值筛选数组元素)⭐
这是本库最方便的特性之一:直接在路径里筛选数组中满足条件的一项。
语法:数组路径[键=值] 或 数组路径[键==值](两种等号都支持)。
条件键不限于
id:id只是最常见的例子,方括号里的「键」可以是该数组项的任意标量成员。JSON 用普通成员名(如name、team、role),XML 用带@前缀的属性名(如@id、@type)。用哪个属性当条件,由你决定。
// 取 match 数组中第一个 id 等于 5 的那一项
doc.Get("match[id=5]");
doc.GetString("match[id=5].name"); // 该项的 name 字段
doc.GetInt("match[id=5].id"); // 该项的 id 字段(=5)
// 换成任意属性同样可以:用 team 字段当条件
doc.GetString("match[team=\"Greuther Fürth\"].score");
// XML:用 @属性 当条件(注意前缀 @)
doc.GetString("person[@id=7].name");
doc.GetString("person[@type=\"guest\"].name");
- 数字按数值比较(
id=5会匹配数值 5,而非字符串 "5")。 - 字符串值需要加引号:
doc.GetString("users[role=\"admin\"].u"); // 双引号
doc.GetString("users[role='guest'].u"); // 单引号均可
- 谓词只返回第一个命中项。找不到则该路径解析失败。
当前
match[id=5]只会返回第一条id=5。若你的数据里id=5有多条、需要「返回全部」,可联系维护者增加match[id=5]*之类的「返回全部」语法。
4.4 路径语法小结
| 写法 | 含义 |
|---|---|
a.b.c |
逐层访问对象成员 |
a[0] / a.0 |
数组第 1 项(索引从 0 开始) |
a[id=5] |
数组里 id 等于 5 的第一项(id 可换成任意标量成员,如 a[team="X"]、a[@type="guest"]) |
a[role="admin"] |
数组里 role 等于 "admin" 的第一项 |
5. 取值方法
拿到路径后,用 DataDocument 上的一系列方法取出目标类型。
5.1 带异常的取值(推荐在「确定存在」时用)
string? s = doc.GetString("user.name");
bool b = doc.GetBool("user.active");
int i = doc.GetInt("user.age");
long l = doc.GetLong("user.bigId");
double d = doc.GetDouble("user.score");
decimal m = doc.GetDecimal("user.price");
DateTime dt = doc.GetDateTime("user.createdAt");
// 拿节点本身(进一步遍历/取子字段)
DataNode node = doc.Get("match[id=5]");
这些
Get*方法在路径解析不到时会抛出DataKitException。取值类型不匹配时(比如对一个非数字字符串调GetInt)也会抛异常。
5.2 带默认值的取值(字符串专用)
// 取不到或为空时返回默认值,不抛异常
string s = doc.GetString("user.nickname", "匿名用户");
5.3 安全取值(不确定是否存在时用)
// 取节点
if (doc.TryGet("a.b.c", out var node))
{
var v = node.AsString();
}
// 直接取某个类型的值
if (doc.TryGetValue<int>("user.age", out var age))
{
Console.WriteLine(age);
}
TryGet / TryGetValue 在路径解析不到时返回 false,不会抛异常。
5.4 在节点上取值
你也可以通过 DataNode 自身的方法来取:
DataNode user = doc.Get("user");
string name = user.GetString("name"); // 相对当前节点的路径
string name2 = user["name"].AsString(); // 直接索引器
int age = user["age"].AsInt();
// 遍历数组
DataNode items = doc.Get("items");
foreach (var item in items.Items)
{
Console.WriteLine(item.GetString("tag"));
}
// 遍历对象成员
foreach (var kv in doc.Get("user").Members)
{
Console.WriteLine($"{kv.Key} = {kv.Value.AsString()}");
}
索引器说明:
node["key"] // 对象按名字取;数组按数字字符串取
node[0] // 数组按索引取(越界抛异常)
5.5 布尔解析的宽容规则
AsBool() / GetBool() 接受:true/false、1/0、yes/no、on/off、是/否(不区分大小写)。例如 INI/TXT 里写的 enabled=yes 也能正确解析成 true。
6. 强类型反序列化
如果某个子树对应你定义好的 C# 类,可直接反序列化成对象(底层用 System.Text.Json):
public class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
public bool Active { get; set; }
}
// 把 user 子树反序列化为 User 对象
User? user = doc.GetValue<User>("user");
// 安全版本
if (doc.TryGetValue<User>("user", out var u))
{
Console.WriteLine(u.Name);
}
重要:
GetValue<T>走的是System.Text.Json默认行为——大小写敏感且按属性名匹配。建议给类加[JsonPropertyName("xxx")]注解,确保字段名与数据源一致:
using System.Text.Json.Serialization;
public class User
{
[JsonPropertyName("name")] public string Name { get; set; } = "";
[JsonPropertyName("age")] public int Age { get; set; }
[JsonPropertyName("active")] public bool Active { get; set; }
}
7. 各格式约定
不同格式在解析成同一棵树时,有一些映射约定需要了解,否则可能取不到值。
7.1 JSON
- 对象 →
Object,数组 →Array,标量(含数字/布尔/null)→Value。 - JSON 的数字、布尔、null 会记住原始类型,因此
GetDouble/As<T>都能得到正确类型(不仅仅是字符串)。
{
"user": { "name": "Ada", "age": 36, "active": true },
"items": [ { "id": 1, "tag": "a" }, { "id": 2, "tag": "b" } ]
}
doc.GetString("user.name"); // "Ada"
doc.GetInt("user.age"); // 36
doc.GetBool("user.active"); // true
doc.GetString("items[1].tag"); // "b"
7.2 XML
约定:
- 根元素本身即路径起点(与 JSON 根对象一致,路径里不含根标签名)。例如根元素是
<root>,则路径从person开始,而不是root.person。 - 元素属性用
@前缀访问:person.@id - 混合文本(元素既有属性/子元素又有直接文本)用
#text访问 - 同名的重复兄弟元素会自动合并成数组
<root>
<person id="7" type="staff">
<name>张三</name>
<phone>123</phone>
<phone>456</phone>
</person>
</root>
doc.GetString("person.@id"); // "7"(属性,根元素 root 是路径起点)
doc.GetString("person.name"); // "张三"
doc.GetString("person.phone[0]"); // "123"(phone 重复 → 数组)
doc.GetString("person.phone[1]"); // "456"
XML 命名空间声明(
xmlns)会被忽略,属性用本地名(LocalName)暴露。
7.3 CSV
- 第一行作为表头,后续每一行被解析成一个对象,用表头字段名作为键。
- 整体是一个
Array,每行是一个Object。 - 支持 RFC-4180:
"..."引号字段、""转义、引号内可含逗号与换行。 - 可通过
doc.Get("0").GetString("列名")按行号访问(行号从 0 开始,对应表头之后的第一行)。
name,age,city
Alice,30,Beijing
Bob,25,Shanghai
doc.Count; // 2(两行数据)
doc.GetString("0.name"); // "Alice"(第 1 行)
doc.GetString("1.city"); // "Shanghai"(第 2 行)
doc.GetInt("0.age"); // 30
表头为空时,该列用列号(0、1、2…)作为键。数据行字段数少于表头时,缺失字段取空字符串。
7.4 纯文本 TXT
ParseText 的判定逻辑:
- 若大多数非空行形如
key: 值或key = 值(含key=value、key: value、key="值"),则整体解析为 Object,按名字取。 - 否则整体解析为 Array(每行一个元素)。
- 原始文本保留在
doc.SourceText中,随时可取。 - 以
#或;开头的行视为注释,会被忽略。 - 同名键出现多次时,自动合并为数组。
# 配置
host = 127.0.0.1
port = 8080
tag = a
tag = b
doc.GetString("host"); // "127.0.0.1"
doc.GetInt("port"); // 8080
doc.GetString("tag[0]"); // "a"(tag 出现两次 → 数组)
doc.GetString("tag[1]"); // "b"
doc.SourceText; // 原始全文
若文本是纯段落(无 key=value 结构):
第一行内容
第二行内容
doc.GetString("0"); // "第一行内容"
doc.GetString("1"); // "第二行内容"
7.5 INI
约定:
[section]下的键值,用doc.GetString("节名.键名")访问。- 不在任何节下的键,归入
_global节:doc.GetString("_global.键名")。 - 支持
key = value、key: value、#/;注释、带引号的值。 - 节名与键名不区分大小写。
[server]
host = 127.0.0.1
port = 5432
[auth]
user = admin
doc.GetString("server.host"); // "127.0.0.1"
doc.GetInt("server.port"); // 5432
doc.GetString("auth.user"); // "admin"
8. 编码与乱码处理
乱码(mojibake)几乎总是因为「字节被用错误的代码页解码」——最典型的就是 UTF-8 字节被当成 Latin-1 / Windows-1252 读取,比如 Greuther Fürth 变成 Greuther Fürth。
8.1 读取文件时指定正确编码
ParseFile 默认就能自动识别大多数情况,绝大多数文件不需要你手动传 Encoding:
- 文件带 BOM(UTF-8 / UTF-16 / UTF-32)→ 自动识别并跳过 BOM;
- 无 BOM 但字节是合法 UTF-8(现代文件几乎都是这种)→ 按 UTF-8 读取;
- 无 BOM 且字节不是合法 UTF-8 → 几乎可以断定是旧的单字节代码页(Latin-1 / Windows-1252),自动回退到 Windows-1252(找不到则用 Latin-1),无需你指定。
也就是说,像 Greuther Fürth 这种按旧代码页保存的文件,现在直接 DataParser.ParseFile("data.json") 就能读到正确中文/德文字符,不用再写 Encoding.Latin1。
只有当你明确知道编码、且想强制覆盖自动判断时,才显式传参数(显式编码永远优先):
using System.Text;
// 强制按 Latin-1 读取(覆盖自动判断)
var doc = DataParser.ParseFile("data.json", Encoding.Latin1);
// 或 Windows-1252
var doc2 = DataParser.ParseFile("data.json", Encoding.GetEncoding("Windows-1252"));
ParseStream 同样支持编码参数,且同样具备上述自动识别/回退逻辑:
using var stream = File.OpenRead("data.json");
var doc = DataParser.ParseStream(stream); // 自动识别
var doc2 = DataParser.ParseStream(stream, Encoding.Latin1); // 强制指定
为什么 Latin-1「不能 100% 单独识别」?因为 Latin-1 把 0–255 每个字节都映射成字符,任何字节流在 Latin-1 下都「合法」,所以它没有签名、没有结构约束,无法自证身份。库能做的最合理推断就是「不是 UTF-8 ⇒ 多半是旧代码页」并回退过去。
8.2 修复「已经被污染」的字符串
如果字符串在上游就已经被错误解码(你拿到的是 "Fürth" 这种),用 TextFixer 修:
using DataKit;
string dirty = GetJsonFromSomewhere(); // 里面是 "Greuther Fürth"
string clean = TextFixer.FixMojibake(dirty); // "Greuther Fürth"
var doc = DataParser.ParseJson(clean);
- 默认会依次尝试用 Latin1 和 Windows-1252 重新取字节、再用 UTF-8 解码,取第一个「无替换符、且内容有变化」的结果。
- 如果你确定当初被错误解码用的是哪个代码页,可以显式传入,避免误判:
string clean = TextFixer.FixMojibake(dirty, Encoding.Latin1);
- 安全兜底:如果修复后仍然含替换符 `` 或内容没有任何变化,则原样返回,绝不会二次损坏本身正确的文本。
TextFixer.FixMojibake("Greuther Fürth"); // 原样返回 "Greuther Fürth"(无变化)
如果你遇到的乱码形态不常见(不是典型的「UTF-8→Latin1」路径),把实际出问题的那一行原文贴给我,我可以在
TextFixer里补充对应的反解码规则,精准修掉它。
9. 重新序列化 / 导出
可以把解析后的文档(或任意子树)重新导成 JSON:
string json = doc.ToJson(); // 整篇文档
string json2 = doc.Get("user").ToJsonString(); // 某个子树
// 拿到 JsonNode,便于继续用 System.Text.Json 处理
System.Text.Json.Nodes.JsonNode? node = doc.ToJsonNode();
注意:XML / CSV / INI / TXT 解析出来的树,重新
ToJson()会得到一份等效的 JSON 表示,方便你在不同格式间转换。
10. 错误处理
解析或取值失败时,统一抛出 DataKitException(自定义异常类型,位于 DataKit 命名空间):
- 格式非法(如 JSON 括号不匹配、XML 标签不闭合)→
Parse*时抛DataKitException。 - 路径解析不到 / 类型不匹配 →
Get/Get*时抛DataKitException。
建议写法:
try
{
int age = doc.GetInt("user.age");
}
catch (DataKitException ex)
{
Console.WriteLine("取值失败:" + ex.Message);
}
// 或者更推荐:用 TryGet / TryGetValue 避免异常
if (doc.TryGetValue<int>("user.age", out var age))
{
// 使用 age
}
11. 完整示例
示例 A:解析 JSON 并取值
using DataKit;
string json = """
{
"user": {
"name": "Ada",
"age": 36,
"address": { "city": "London" },
"roles": ["admin", "editor"]
},
"match": [
{ "id": 3, "name": "alpha" },
{ "id": 5, "name": "beta" }
]
}
""";
var doc = DataParser.ParseJson(json);
string name = doc.GetString("user.name"); // "Ada"
int age = doc.GetInt("user.age"); // 36
string city = doc.GetString("user.address.city");// "London"
string role0 = doc.GetString("user.roles[0]"); // "admin"
string m5 = doc.GetString("match[id=5].name"); // "beta"(谓词过滤)
Console.WriteLine($"{name}, {age}, {city}, {role0}, {m5}");
示例 B:解析 XML 文件 + 处理编码
using DataKit;
using System.Text;
// 文件按 Latin-1 保存,显式指定编码避免乱码
var doc = DataParser.ParseFile("people.xml", Encoding.Latin1);
string id = doc.GetString("person.@id"); // 属性用 @,根元素 root 是路径起点
string name = doc.GetString("person.name");
示例 C:解析 CSV 并遍历
using DataKit;
var doc = DataParser.ParseCsv(csvText);
for (int r = 0; r < doc.Count; r++)
{
string name = doc.GetString($"{r}.name");
int age = doc.GetInt($"{r}.age");
Console.WriteLine($"{name} ({age})");
}
示例 D:解析 TXT / INI 配置
using DataKit;
var cfg = DataParser.ParseIni(iniText);
string host = cfg.GetString("server.host");
int port = cfg.GetInt("server.port");
var txt = DataParser.ParseText(textContent);
if (txt.TryGet("title", out var title))
{
Console.WriteLine(title.AsString());
}
示例 E:从 REST API 获取数据
REST 响应本质是「字符串或字节流」,直接喂给 DataParser 即可,与来源无关。
using DataKit;
using System.Net.Http;
var http = new HttpClient();
// 模式 1:拿到字符串(HttpClient 已按 Content-Type 的 charset 解码成 .NET 字符串)
string body = await http.GetStringAsync("https://api.example.com/user/1");
var doc = DataParser.ParseJson(body); // 已知是 JSON
// 或不确定格式:var doc = DataParser.Parse(body); // 以 { 或 [ 开头 → JSON,以 < 开头 → XML
string name = doc.GetString("user.name");
string tag = doc.GetString("match[id=5].n"); // 沿用路径谓词语法
// 模式 2:拿到流(更省内存,不先把整个响应拼成字符串)
using var stream = await http.GetStreamAsync("https://api.example.com/user/1");
var doc2 = DataParser.ParseStream(stream, format: DataFormat.Json);
int age = doc2.GetInt("user.age");
// XML 响应同理
string xmlBody = await http.GetStringAsync("https://api.example.com/person/7");
var xmlDoc = DataParser.ParseXml(xmlBody);
string pid = xmlDoc.GetString("person.@id"); // 根元素 person 是路径起点
关于编码:REST 响应的字节已由
HttpClient按Content-Type里的 charset(默认 UTF-8)正确解码成字符串,所以一般不会出现文件那种乱码问题。只有当接口错误地声明了 charset、导致字符串已经被污染时,才需要TextFixer.FixMojibake(body)兜底修复。
12. 常见问题 FAQ
Q1:我的 .NET Framework 4.x 项目能用吗?
A:类库默认 net9.0。需要把 DataKit.csproj 里的 TargetFramework 改成 netstandard2.0 后重新编译,即可被 .NET Framework 4.6.1+ 引用(功能基本一致,部分新语法会退化为兼容写法)。
Q2:match[id=5] 返回多条 id=5 时怎么办?
A:当前只返回第一条命中。如需「全部」,请在路径语法中增加「返回全部」标记(如 match[id=5]*),可联系维护者补充。
Q3:取值类型不对会怎样?
A:GetInt("name") 对字符串 "Ada" 会抛 DataKitException。不确定的字段优先用 TryGetValue<T>。
Q4:为什么 GetValue<User> 反序列化出来是 null / 字段为空?
A:底层是 System.Text.Json,默认大小写敏感。确认类属性名与 JSON 键一致,或加 [JsonPropertyName("xxx")]。
Q5:中文/特殊字符乱码怎么解决?
A:见第 8 节。优先在 ParseFile/ParseStream 传入正确 Encoding;若字符串已污染,用 TextFixer.FixMojibake 修复。
Q6:能不能解析超大文件(几百 MB)? A:当前解析会把内容一次性读入内存。超大文件场景可联系维护者增加「流式解析」模式。
Q7:支持 YAML 吗? A:当前支持 JSON / XML / TXT / CSV / INI。YAML 可按需补充。
文档与源码保持一致。
DataKit是零依赖、线程安全的类库,所有静态入口方法均可安全并发调用。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- System.Text.Encoding.CodePages (>= 8.0.0)
-
net9.0
- System.Text.Encoding.CodePages (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.