项目背景

在银行卡、IC 卡等金融支付领域,经常需要处理 TLV 格式的数据。本文将展示一个完整的 TLV 解析器实现,代码可以直接复制使用。

核心功能:

  • 递归解析嵌套 TLV 结构
  • 多种编码方式(HEX、BCD、ASCII、UTF-8、GBK)
  • 银行卡专用 Tag 映射
  • 树形可视化输出
  • 扩展方法支持

代码结构

本解析器由三个主要部分组成:

  1. TLV 入口方法 - 快速使用的示例入口
  2. TlvNode 类 - TLV 节点数据结构
  3. TLV 解析方法 - 核心解析逻辑

TLV 入口方法

这是一个快速使用的示例入口,展示了如何解析 TLV 数据并输出树形结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#region Tlv 入口
public static void TlvMain()
{
StringBuilder sb = new StringBuilder();
var description = "示例TLV数据解析";
var data = "07094333353138303631329000";

// 使用扩展方法将十六进制字符串转为字节数组
var dataBytes = data.ToHexBytes('\0');

sb.AppendLine($"------ {description} 开始 ------");
sb.AppendLine($"原始Hex:{data}");

// 使用扩展方法解析并输出树形结构
sb.AppendLine(dataBytes.ToTlvNodes().ToTlvTreeString());

sb.AppendLine($"------ {description} 结束 ------");
Console.WriteLine(sb.ToString());
}
#endregion

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
------ 示例TLV数据解析 开始 ------
原始Hex:07094333353138303631329000
├─ 07 (07)
│ Length: 9, Constructed: False
│ utf8Value: C35180612
│ GbkValue: 解析失败
│ BcdValue: 433335313830363132
│ AsciiValue: C35180612
│ Hex: 433335313830363132

├─ 90 (成功)
│ Length: 0, Constructed: False
│ utf8Value: 无数据
│ GbkValue: 无数据
│ BcdValue: 无数据
│ AsciiValue: 无数据
│ Hex:


------ 示例TLV数据解析 结束 ------

TlvNode 类

这是 TLV 节点的完整实现,包含所有编码方式和银行卡 Tag 映射。由于代码较长,这里展示完整的类结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
/// <summary>
/// TLV节点类,支持层次化结构和多种编码解析
/// </summary>
public class TlvNode
{
#region 基本属性

/// <summary>
/// 标签(Tag)- TLV结构中的T部分
/// </summary>
public string Tag { get; set; }

/// <summary>
/// 长度(Length)- TLV结构中的L部分,表示Value的字节长度
/// </summary>
public int Length { get; set; }

/// <summary>
/// 原始字节值(Raw Value)- TLV结构中的V部分的原始字节数据
/// </summary>
public byte[] RawValue { get; set; }

/// <summary>
/// 十六进制字符串值 - RawValue的十六进制表示
/// </summary>
public string HexValue { get; set; }

/// <summary>
/// 子节点列表 - 当前节点为构造型时包含的嵌套TLV节点
/// </summary>
public List<TlvNode> Children { get; set; } = new List<TlvNode>();

/// <summary>
/// 是否为构造型标签 - 构造型标签的Value部分包含嵌套的TLV结构
/// </summary>
public bool IsConstructed { get; set; }

#endregion

#region 编码解析属性

/// <summary>
/// UTF-8编码解析值 - 将RawValue按UTF-8编码解析为字符串
/// 如果解析失败或结果为空,返回"已加密"
/// </summary>
public string Utf8Value
{
get
{
try
{
if (RawValue == null || RawValue.Length == 0)
return "无数据";

var value = Encoding.UTF8.GetString(RawValue).Trim('\0', ' ');
return string.IsNullOrEmpty(value) ? "已加密" : value;
}
catch
{
return "解析失败";
}
}
}

/// <summary>
/// GBK编码解析值 - 将RawValue按GBK编码解析为字符串
/// 如果解析失败或结果为空,返回"无数据"
/// </summary>
public string GbkValue
{
get
{
try
{
if (RawValue == null || RawValue.Length == 0)
return "无数据";

// 注册GBK编码提供程序(.NET Core需要)
//Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var gbkEncoding = Encoding.GetEncoding("GBK");

var value = gbkEncoding.GetString(RawValue).Trim('\0', ' ');
return string.IsNullOrEmpty(value) ? "已加密" : value;
}
catch
{
return "解析失败";
}
}
}

/// <summary>
/// ASCII编码解析值 - 将RawValue按ASCII编码解析为字符串
/// 主要用于处理银行卡号等纯ASCII数据
/// </summary>
public string AsciiValue
{
get
{
try
{
if (RawValue == null || RawValue.Length == 0)
return "无数据";

var value = Encoding.ASCII.GetString(RawValue).Trim('\0', ' ');
return string.IsNullOrEmpty(value) ? "已加密" : value;
}
catch
{
return "解析失败";
}
}
}

/// <summary>
/// BCD编码解析值 - 将RawValue按BCD编码解析为数字字符串
/// 主要用于处理银行卡号、金额等数值数据
/// </summary>
public string BcdValue
{
get
{
try
{
if (RawValue == null || RawValue.Length == 0)
return "无数据";

var sb = new StringBuilder();
foreach (byte b in RawValue)
{
sb.Append((b >> 4) & 0x0F); // 高位
sb.Append(b & 0x0F); // 低位
}

var value = sb.ToString().TrimEnd('F');
return string.IsNullOrEmpty(value) ? "已加密" : value;
}
catch
{
return "解析失败";
}
}
}

#endregion

#region 银行卡标签映射

/// <summary>
/// 银行卡标签映射字典 - 将技术标签转换为业务含义
/// </summary>
private static readonly Dictionary<string, string> BankCardTagMapping = new Dictionary<
string,
string
>(StringComparer.OrdinalIgnoreCase)
{
{ "4F", "银行卡AID" },
{ "57", "卡号" },
{ "5F20", "持卡人姓名" },
{ "9F1F", "磁条1自定义数据" },
{ "9F61", "持卡人身份证" },
{ "6F12", "IC卡类型" },
{ "9F12", "IC卡类型" },
{ "50", "银行卡类型" },
{ "C2", "银行类型" },
{ "9F79", "银行卡余额" },
{ "9F78", "电子交易限额" },
{ "9F74", "交易授权码" },
{ "9F62", "持卡人证件类型" },
};

private static readonly Dictionary<string, string> CommonTagMapping = new Dictionary<
string,
string
>(StringComparer.OrdinalIgnoreCase)
{
{ "90", "成功" },
};

private static readonly Dictionary<string, string> SSC_TagMapping = new Dictionary<
string,
string
>(StringComparer.OrdinalIgnoreCase)
{ };

private static readonly Dictionary<string, string> CombinedTagMapping = BankCardTagMapping
.Union(CommonTagMapping)
.Union(SSC_TagMapping)
.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase);

/// <summary>
/// 标签名称 - 将技术标签转换为业务含义,如果没有映射则返回原标签
/// </summary>
public string TagName
{
get => CombinedTagMapping.TryGetValue(Tag, out string name) ? name : Tag;
}

/// <summary>
/// 银行卡专用解析值 - 根据标签类型选择合适的编码方式解析
/// </summary>
public string BankCardValue
{
get
{
switch (Tag?.ToUpper())
{
case "57": // 卡号 - 需要特殊处理,去掉D分隔符后的部分
try
{
var hexStr = HexValue;
var dIndex = hexStr.IndexOf('D');
if (dIndex > 0)
{
hexStr = hexStr.Substring(0, dIndex);
}
return hexStr;
}
catch
{
return BcdValue;
}

case "9F62": // 持卡人证件类型 - 特殊值映射
if (HexValue == "00")
return "身份证";
return Utf8Value;

case "5F20": // 持卡人姓名 - 优先使用GBK编码
case "50": // 银行卡类型
return GbkValue;

case "9F79": // 银行卡余额 - BCD编码的金额
case "9F78": // 电子交易限额
return BcdValue;

case "4F": // 银行卡AID - 十六进制值
case "9F1F": // 磁条1自定义数据
case "9F74": // 交易授权码
return HexValue;

case "9F61": // 持卡人身份证 - ASCII或BCD
var asciiResult = AsciiValue;
return asciiResult != "已加密" ? asciiResult : BcdValue;

default:
// 默认尝试UTF-8,失败则尝试GBK,最后返回十六进制
var utf8Result = Utf8Value;
if (utf8Result != "已加密")
return utf8Result;

var gbkResult = GbkValue;
if (gbkResult != "已加密")
return gbkResult;

return HexValue;
}
}
}

#endregion

#region 子节点查询方法

/// <summary>
/// 获取指定标签的第一个子节点
/// </summary>
/// <param name="tag">要查找的标签</param>
/// <returns>找到的子节点,如果没有找到返回null</returns>
public TlvNode? GetChild(string tag)
{
return Children.FirstOrDefault(c => c.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase));
}

/// <summary>
/// 获取指定标签的所有子节点
/// </summary>
/// <param name="tag">要查找的标签</param>
/// <returns>找到的子节点列表</returns>
public List<TlvNode> GetChildren(string tag)
{
return Children.Where(c => c.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase)).ToList();
}

/// <summary>
/// 递归查找指定标签的节点(包括当前节点和所有子孙节点)
/// </summary>
/// <param name="tag">要查找的标签</param>
/// <returns>找到的第一个节点,如果没有找到返回null</returns>
public TlvNode? FindDescendant(string tag)
{
if (Tag.Equals(tag, StringComparison.OrdinalIgnoreCase))
return this;

foreach (var child in Children)
{
var found = child.FindDescendant(tag);
if (found != null)
return found;
}

return null;
}

#endregion

#region 调试和显示方法

/// <summary>
/// 获取节点的字符串表示(用于调试)
/// </summary>
/// <returns>包含标签、长度、值等信息的字符串</returns>
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine($"Tag: {Tag} ({TagName})");
sb.AppendLine($"Length: {Length}");
sb.AppendLine($"Constructed: {IsConstructed}");
sb.AppendLine($"HexValue: {HexValue}");
sb.AppendLine($"UTF8Value: {Utf8Value}");
sb.AppendLine($"GBKValue: {GbkValue}");
sb.AppendLine($"BankCardValue: {BankCardValue}");

if (Children.Any())
{
sb.AppendLine($"Children Count: {Children.Count}");
}

return sb.ToString();
}

/// <summary>
/// 获取节点树的字符串表示(用于调试)
/// </summary>
/// <param name="indent">缩进级别</param>
/// <returns>包含当前节点及所有子节点的树形字符串</returns>
public string ToTreeString(int indent = 0)
{
var sb = new StringBuilder();
var indentStr = new string(' ', indent * 2);

sb.AppendLine($"{indentStr}├─ {Tag} ({TagName})");
sb.AppendLine($"{indentStr}│ Length: {Length}, Constructed: {IsConstructed}");
sb.AppendLine($"{indentStr}│ utf8Value: {Utf8Value}");
sb.AppendLine($"{indentStr}│ GbkValue: {GbkValue}");
sb.AppendLine($"{indentStr}│ BcdValue: {BcdValue}");
sb.AppendLine($"{indentStr}│ AsciiValue: {AsciiValue}");
sb.AppendLine($"{indentStr}│ Hex: {HexValue}");

foreach (var child in Children)
{
sb.Append(child.ToTreeString(indent + 1));
}

return sb.ToString();
}

#endregion
}

TlvNode 关键特性

1. 多编码自动解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public string Utf8Value
{
get
{
try
{
if (RawValue == null || RawValue.Length == 0)
return "无数据";

var value = Encoding.UTF8.GetString(RawValue).Trim('\0', ' ');
return string.IsNullOrEmpty(value) ? "已加密" : value;
}
catch
{
return "解析失败";
}
}
}

// 类似实现:GbkValue, AsciiValue, BcdValue

2. 银行卡 Tag 映射

1
2
3
4
5
6
7
8
9
10
private static readonly Dictionary<string, string> BankCardTagMapping =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "4F", "银行卡AID" },
{ "57", "卡号" },
{ "5F20", "持卡人姓名" },
{ "9F61", "持卡人身份证" },
{ "9F79", "银行卡余额" },
// ... 更多映射
};

3. 智能编码选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public string BankCardValue
{
get
{
switch (Tag?.ToUpper())
{
case "57": // 卡号 - 特殊处理
case "5F20": // 姓名 - GBK编码
case "9F79": // 余额 - BCD编码
case "4F": // AID - HEX编码
// ... 根据Tag自动选择合适的编码
}
}
}

TLV 解析方法

核心解析逻辑,包含递归解析、辅助方法和扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

#region TLV解析

#region 递归解析方法

/// <summary>
/// 递归解析TLV数据,返回层次化结构
/// </summary>
/// <param name="data">要解析的数据</param>
/// <param name="maxDepth">最大递归深度,防止无限递归</param>
/// <returns>TLV节点列表</returns>
public static List<TlvNode> ParseTlvRecursive(byte[] data, int maxDepth = 10)
{
return ParseTlvRecursive(data, 0, data.Length, 0, maxDepth);
}

/// <summary>
/// 递归解析TLV数据(重载,支持十六进制字符串输入)
/// </summary>
public static List<TlvNode> ParseTlvRecursive(
string hexString,
char separator = ' ',
int maxDepth = 10
)
{
var data = ParseHexString(hexString, separator);
return ParseTlvRecursive(data, maxDepth);
}

/// <summary>
/// 内部递归解析方法
/// </summary>
private static List<TlvNode> ParseTlvRecursive(
byte[] data,
int startIndex,
int endIndex,
int currentDepth,
int maxDepth
)
{
var result = new List<TlvNode>();
int index = startIndex;

while (index < endIndex && currentDepth < maxDepth)
{
var node = ParseSingleTlv(data, ref index);
if (node == null)
break;

// 如果是构造型标签,尝试递归解析其内容
if (node.IsConstructed && node.RawValue.Length > 0 && currentDepth < maxDepth - 1)
{
try
{
node.Children = ParseTlvRecursive(
node.RawValue,
0,
node.RawValue.Length,
currentDepth + 1,
maxDepth
);
}
catch
{
// 如果递归解析失败,保持原始数据
node.Children.Clear();
}
}

result.Add(node);
}

return result;
}

/// <summary>
/// 解析单个TLV节点
/// </summary>
private static TlvNode? ParseSingleTlv(byte[] data, ref int index)
{
if (index >= data.Length)
return null;

// 读取 Tag
var tagResult = ReadTag(data, ref index);
if (tagResult.tag == null)
return null;

// 读取 Length
if (index >= data.Length)
return null;

var lengthResult = ReadLength(data, ref index);
if (lengthResult.length < 0)
return null;

// 验证是否有足够的数据
if (index + lengthResult.length > data.Length)
return null;

// 读取 Value
byte[] value = new byte[lengthResult.length];
Array.Copy(data, index, value, 0, lengthResult.length);
index += lengthResult.length;

return new TlvNode
{
Tag = tagResult.tag,
Length = lengthResult.length,
RawValue = value,
HexValue = BitConverter.ToString(value).Replace("-", ""),
IsConstructed = tagResult.isConstructed,
};
}

/// <summary>
/// 读取Tag字段
/// </summary>
private static (string tag, bool isConstructed) ReadTag(byte[] data, ref int index)
{
if (index >= data.Length)
return (null, false);

byte firstByte = data[index++];
bool isConstructed = IsConstructedTag(firstByte);
string tag = firstByte.ToString("X2");

// 检查是否为多字节tag
if ((firstByte & 0x1F) == 0x1F && index < data.Length)
{
// 多字节tag处理
do
{
if (index >= data.Length)
return (null, false);

byte nextByte = data[index++];
tag += nextByte.ToString("X2");

// 如果最高位为0,表示tag结束
if ((nextByte & 0x80) == 0)
break;
} while (index < data.Length);
}

return (tag, isConstructed);
}

/// <summary>
/// 读取Length字段
/// </summary>
private static (int length, bool isValid) ReadLength(byte[] data, ref int index)
{
if (index >= data.Length)
return (-1, false);

int length = data[index++];

// 短格式:最高位为0
if (length <= 0x7F)
{
return (length, true);
}

// 长格式:最高位为1
if (length == 0x80)
{
// 不定长格式,在TLV中通常不使用
return (-1, false);
}

// 长格式:低7位表示后续长度字节的数量
int lenOfLen = length & 0x7F;
if (lenOfLen > 4 || index + lenOfLen > data.Length)
{
// 长度字节数超过4或数据不足
return (-1, false);
}

length = 0;
for (int i = 0; i < lenOfLen; i++)
{
length = (length << 8) | data[index++];
}

return (length, true);
}

#endregion

#region 辅助方法

/// <summary>
/// 解析十六进制字符串为字节数组(修复版本)
/// </summary>
public static byte[] ParseHexString(string hexString, char separator = ' ')
{
if (string.IsNullOrWhiteSpace(hexString))
return Array.Empty<byte>();
hexString = hexString.Replace('\0'.ToString(), "");
// 处理紧凑格式(无分隔符)或带有用户指定分隔符的情况
string cleanHex =
(separator != '\0')
? hexString.Replace(separator.ToString(), "") // 移除指定的分隔符
: RemoveNonHexCharacters(hexString); // 紧凑模式下移除所有非十六进制字符

// 验证处理后字符串的有效性
if (cleanHex.Length == 0)
return Array.Empty<byte>();

if (cleanHex.Length % 2 != 0)
throw new ArgumentException("十六进制字符串长度必须为偶数位");

// 修复:使用 cleanHex 而不是 hexString
var result = Enumerable
.Range(0, cleanHex.Length / 2)
.Select(x => Convert.ToByte(cleanHex.Substring(x * 2, 2), 16))
.ToArray();
return result;
}

// 辅助方法:移除非十六进制字符(用于紧凑模式)
private static string RemoveNonHexCharacters(string input)
{
StringBuilder result = new StringBuilder();
foreach (char c in input)
{
if (IsHexCharacter(c))
result.Append(c);
}
return result.ToString().ToUpperInvariant(); // 统一转为大写
}

// 辅助方法:检查是否为有效的十六进制字符
private static bool IsHexCharacter(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}

/// <summary>
/// 判断tag是否为构造型(Constructed)
/// 第6位(从右数起第6位)为1表示构造型
/// </summary>
private static bool IsConstructedTag(byte firstTagByte)
{
return (firstTagByte & 0x20) != 0;
}

#endregion

#region 辅助查询方法

/// <summary>
/// 在TLV节点列表中查找指定标签的节点
/// </summary>
public static TlvNode? FindNode(List<TlvNode> nodes, string tag)
{
foreach (var node in nodes)
{
if (node.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase))
return node;

if (node.Children.Any())
{
var found = FindNode(node.Children, tag);
if (found != null)
return found;
}
}
return null;
}

/// <summary>
/// 获取TLV结构的字符串表示(用于调试)
/// </summary>
public static string GetTreeString(List<TlvNode> nodes, int indent = 0)
{
var sb = new StringBuilder();
string indentStr = new string(' ', indent * 2);

foreach (var node in nodes)
{
sb.AppendLine(
$"{indentStr}Tag: {node.Tag}, Length: {node.Length}, Constructed: {node.IsConstructed}"
);
sb.AppendLine($"{indentStr}Value: {node.HexValue}");

if (node.Children.Any())
{
sb.AppendLine($"{indentStr}Children:");
sb.Append(GetTreeString(node.Children, indent + 1));
}
}

return sb.ToString();
}

/// <summary>
/// 获取TLV结构的字符串表示(用于调试)
/// </summary>
public static string GetDefTreeString(List<TlvNode> nodes, int indent = 0)
{
var sb = new StringBuilder();
string indentStr = new string(' ', indent * 2);

foreach (var node in nodes)
{
sb.AppendLine(node.ToTreeString(indent));
}

return sb.ToString();
}

#endregion

#region TLV 字节扩展方法

public static List<TlvNode> ToTlvNodes(this byte[] bytes, int maxDepth = 10)
{
return ParseTlvRecursive(bytes, maxDepth);
}

public static string ToTlvTreeString(
this List<TlvNode> nodes,
int indent = 0,
string type = "1"
)
{
var str = type == "1" ? GetDefTreeString(nodes, indent) : GetTreeString(nodes);
return str;
}

public static byte[] ToHexBytes(this string hexString, char separator = ' ')
{
return ParseHexString(hexString, separator);
}
#endregion

#endregion

使用示例

基本使用

1
2
3
4
// 示例1:解析简单TLV数据
var data = "07094333353138303631329000";
var nodes = data.ToHexBytes('\0').ToTlvNodes();
Console.WriteLine(nodes.ToTlvTreeString());

银行卡数据解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 示例2:解析银行卡数据
var cardData = "5709622512345678901290005F200CD5C5C8FD";
var nodes = cardData.ToHexBytes('\0').ToTlvNodes();

// 查找特定Tag
var cardNoNode = nodes.FirstOrDefault(n => n.Tag == "57");
if (cardNoNode != null)
{
Console.WriteLine($"卡号: {cardNoNode.BankCardValue}");
}

var nameNode = nodes.FirstOrDefault(n => n.Tag == "5F20");
if (nameNode != null)
{
Console.WriteLine($"姓名: {nameNode.BankCardValue}");
}

嵌套结构解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 示例3:解析嵌套TLV结构
var nestedData = "702957096225123456789012900...";
var nodes = nestedData.ToHexBytes('\0').ToTlvNodes();

// 递归查找子节点
foreach (var node in nodes)
{
Console.WriteLine($"Tag: {node.TagName}");
if (node.Children.Any())
{
foreach (var child in node.Children)
{
Console.WriteLine($" ├─ {child.TagName}: {child.BankCardValue}");
}
}
}

关键特性说明

1. 多编码支持

  • UTF-8 - 通用 Unicode 编码
  • GBK - 中文编码(持卡人姓名)
  • ASCII - 纯英文字符
  • BCD - 数字编码(卡号、金额)
  • HEX - 十六进制原始值

2. 银行卡 Tag 映射

支持常见的银行卡 Tag 自动识别和命名:

  • 57 → 卡号
  • 5F20 → 持卡人姓名
  • 9F61 → 持卡人身份证
  • 9F79 → 银行卡余额
  • 更多…

3. 递归解析

自动识别构造型 Tag 并递归解析嵌套结构,支持任意层级的 TLV 嵌套。

4. 扩展方法

提供便捷的扩展方法:

  • ToHexBytes() - 字符串转字节数组
  • ToTlvNodes() - 字节数组转 TLV 节点
  • ToTlvTreeString() - 生成树形输出

注意事项

  1. GBK 编码 - .NET Core 需要注册编码提供程序:

    1
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  2. 递归深度 - 默认最大递归深度为 10,可根据需要调整:

    1
    var nodes = data.ToTlvNodes(maxDepth: 20);
  3. Tag 映射扩展 - 可以在 BankCardTagMapping字典中添加自定义 Tag:

    1
    2
    { "9F26", "应用密文" },
    { "9F27", "密文信息数据" },
  4. 分隔符处理 - ToHexBytes支持不同的分隔符:

    1
    2
    3
    "57 09 43 33".ToHexBytes(' ')  // 空格分隔
    "57-09-43-33".ToHexBytes('-') // 横线分隔
    "57094333".ToHexBytes('\0') // 无分隔符

总结

这个 TLV 解析器具有以下优势:

  1. 代码完整 - 可以直接复制使用,无需额外修改
  2. 功能全面 - 支持递归解析、多种编码、Tag 映射
  3. 易于扩展 - 可以轻松添加新的 Tag 映射和编码方式
  4. 使用简单 - 提供扩展方法,一行代码即可解析

适用于银行卡读卡、IC 卡数据解析、支付终端开发等场景。


在实际项目中遇到数据解析需求,于是编写了这个解析辅助函数