〇、简介
YAML(Yet Another Markup Language)另一种标记语言。
YAML 是一种较为人性化的数据序列化语言,可以配合目前大多数编程语言使用。YAML 的语法比较简洁直观,特点是使用空格来表达层次结构,其最大优势在于数据结构方面的表达,所以 YAML 更多应用于编写配置文件,其文件一般以 .yml 为后缀。
特点:
- 易于阅读:YAML 使用缩进和比较简洁的语法来表示数据结构,使得它比许多其他数据格式更容易阅读和理解。
- 数据结构友好:YAML 天然支持标量(如字符串、整数、浮点数)、列表(数组)和映射(字典)等数据结构。
- 无类型标签:YAML 通过上下文来推断值的类型,不需要显式的类型标签。
- 可交互:YAML 可以在不同的编程语言之间进行交互,因为它有广泛的语言支持。
- 表达能力强:YAML 可以表示复杂的数据结构,并且可以通过锚点和别名来重用数据。
- 可伸缩性:YAML 可以很容易地扩展到新的数据类型,而不需要改变现有的解析器。
YAML 的使用场景包括但不限于:应用程序的配置、数据交换格式、文档撰写、自动化脚本、云计算和服务编排等等。
一、YAML 语法
1.1 基本语法
- 大小写敏感。
- 使用缩进表示层级关系。
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。
# YAML one: two: 2 three: four: 4 five: 5
// 转成 JSON 后的格式 "one": { "two": 2, "three": { "four": 4, "five": 5 } }
- 用 # 标识注释,且只能单行
# 我是一行注释 # 我是另一行注释
- 一个 YAML 文件可以包含多个文档
每个文档均以“---”三个横杠开始,如果一个文件中仅一个文档,则可省略。
每个文档并不必须使用结束符“...”来表示结束,但是对于网络传输或者流来说,作为明确结束的符号,有利于软件处理。(例如,不需要知道流关闭就能知道文档结束)
--- # 这是第一份文档内容 one: 1 # 其他内容... ... --- # 这是第二份文档内容 two: 2 # 其他内容...
1.2 数据结构与类型
1.2.1 对象 Mapping
标识以键值对(key: value)形式出现的数据。
- 格式
在键和值中间加入标识,冒号+空格(: )。
# YAML key: value
// JSON { "key": "value" }
- 多层嵌套数据
用缩进表示层级关系
# YAML key: child-key1: value1 child-key2: value2
// JSON { "key": { "child-key1": "value1", "child-key2": "value2", } }
- 用一对 {} 花括号包裹,表示一个键值表
键值对之间用逗号+空格(, )分隔,类似 JSON。
# YAML key: { child-key1: value1, child-key2: value2 }
// JSON { "key": { "child-key1": "value1", "child-key2": "value2" } }
- 问号+空格(? )表示复杂的键
当键是一个列表或键值表时,就需要使用本符号来标记。
# 使用一个列表作为键 [blue, reg, green]: Color # 等价于 ? - blue - reg - gree : Color
// JSON { "blue,reg,gree": "Color" }
- 多种组合表示
每个结构都可以嵌套组成复杂的表示结构。
# YAML div: - border: {color: red, width: 2px} - background: {color: green} - padding: [0, 10px, 0, 10px]
// JSON { "div": [ { "border": { "color": "red", "width": "2px" } }, { "background": { "color": "green" } }, { "padding": [0, "10px", 0, "10px"] } ] }
# YAML items: - item: cpu model: i3 price: ¥800.00 - item: HD model: WD price: ¥450.00
// JSON { "items": [ { "item": "cpu", "model": "i3", "price": "¥800.00" }, { "item": "HD", "model": "WD", "price": "¥450.00" } ] }
1.2.2 数组 Sequence
- 横线+空格(- )开头的数据组成一个数组
# YAML 区块格式(Block Format) values: - value1 - value2 - value3 # YAML 内联格式(Inline Format) values: [value1, value2, value3]
// JSON { "values": [ "value1", "value2", "value3" ] }
- 多维数组
# YAML values: - - value1 - value2 - - value3 - value4
// JSON { "values": [ [ "value1", "value2" ], [ "value3", "value4" ] ] }
- 数组组合
# YAML - [blue, red, green] # 列表项本身也是一个列表 - [Age, Bag] - site: {osc:www.oschina.net, baidu: www.baidu.com} # 这里是同 键值表 组合表示
// JSON [ [ "blue", "red", "green" ], [ "Age", "Bag" ], { "site": { "osc:www.oschina.net": null, "baidu": "www.baidu.com" } } ]
- 复合结构
# YAML languages: - Ruby - Perl - Python websites: YAML: yaml.org Ruby: ruby-lang.org Python: python.org Perl: use.perl.org
// JSON { "languages": [ "Ruby", "Perl", "Python" ], "websites": { "YAML": "yaml.org", "Ruby": "ruby-lang.org", "Python": "python.org", "Perl": "use.perl.org" } }
1.2.3 标量 Scalars 基本数据类型-str、bool、int、float、null、datetime...
本章节包含以下部分简介:字符串 String、布尔值 boolean、整数 Integer、浮点数 Float、空 Null、日期时间 datetime、类型强制转换等。
- 字符串(string、str)
字符串是最常见,也是最复杂的一种数据类型。
字符串一般不需要用引号包裹,但是如果字符串中使用了反斜杠“”开头的转义字符就必须使用引号包裹。
# YAML strings: - Hello without quote # 不用引号包裹 - Hello world # 拆成多行后会自动在中间添加空格 - 'Hello with single quotes' # 单引号包裹 - "Hello with double quotes" # 双引号包裹 - "I am fine. u263A" # 使用双引号包裹时支持 Unicode 编码 - "x0dx0a is rn" # 使用双引号包裹时还支持 Hex 编码 - 'He said: "Hello!"' # 单双引号支持嵌套"
// JSON { "strings": [ "Hello without quote", "Hello world", "Hello with single quotes", "Hello with double quotes", "I am fine. ☺", "rn is rn", "He said: "Hello!"" ] }
用竖线符“ | ”来表示保留换行(Newlines preserved)。
每行的前边缩进和后边的空白会被去掉,而额外的缩进和行后的空格会被保留。
# YAML lines: | 我是第一行 我是第二行 我是吴彦祖 我是第四行 我是第五行
// JSON { "lines": "我是第一行 n我是第二行n 我是吴彦祖n 我是第四行n我是第五行n" }
用右尖括号“ > ”来表示折叠换行(Newlines folded)。
只有空白行才会被识别为换行,原来的换行符都会被转换成空格。最后也会以换行符结束。
# YAML lines: > 我是第一行 我也是第一行 我仍是第一行 我依旧是第一行 我是第二行 这么巧我也是第二行
// JSON { "lines": "我是第一行 我也是第一行 我仍是第一行 我依旧是第一行n我是第二行 这么巧我也是第二行n" }
- 布尔值(Boolean、bool)
经测试,只有全部大写、全部小写、首字母大写这三种情况,可以自动识别为布尔值。其他情况均转成字符串,如下:
# YAML boolean: - true - True - TRUE - TRue - false - False - FALSE - FAlse
// JSON { "boolean": [ true, true, true, "TRue", false, false, false, "FAlse" ] }
- 整数(Integer、int)
YAML 允许二进制的整数,但前边需要带上标识:‘0b’。
# YAML int: - 666 - 0b0010_1110 # 二进制表示
//JSON { "int": [ 666, 46 ] }
- 浮点数(Floating-point、float)
允许使用科学计数法,如下代码:
# YAML float: - 3.14 - 6.8523015e+5 # 使用科学计数法
// JSON { "float": [ 3.14, 685230.15 ] }
- 空(Null)
# YAML nulls: - null - Null - ~ - # 未指定值
// JSON { "nulls": [ null, null, null, null ] }
- 日期时间(date、datetime)
没有 +8 小时的标记时,默认就是协调世界时(UTC),也就是标准时间,转换成 JSON 都是按照协调世界时的格式,如下代码:
# YAML dates: - 2024-03-05 # 协调世界时(UTC) - 2024-03-05T20:00:00 # 协调世界时(UTC) - 2024-03-05T20:00:00+08:00 # +8 小时就是北京时间 - 2024-03-05T20:00:00.10+08:00 - 2024-03-05 20:00:00.10 +8
// JSON { "dates": [ "2024-03-05T00:00:00.000Z", "2024-03-05T20:00:00.000Z", "2024-03-05T12:00:00.000Z", "2024-03-05T12:00:00.100Z", "2024-03-05T12:00:00.100Z" ] }
- 类型转换(双叹号:!!)
YAML 支持使用严格类型标签:“!!”(格式:双感叹号+目标类型),来强制转换类型,如下代码:
# YAML strings_convert: - !!float '666' # 字符串转浮点数 - '666' - !!str 666 # 整数转为字符串 - !!str 666.66 # 浮点数转为字符串 - !!str true # 布尔值转为字符串 - !!bool 'true' # 字符串转布尔值
// JSON { "strings_convert": [ 666, "666", "666", "666.66", "true", true ] }
1.3 数据重用和合并(&、*、<<)
为了保持内容的简洁,避免过多重复的定义,YAML 提供了由锚点标签“&”和引用标签“*”组成的语法,利用这套语法可以快速引用相同的一些数据。如下代码:
# YAML a: &anchor # 设置锚点 one: 1 two: 2 three: 3 b: *anchor # 引用锚点
// JSON { "a": { "one": 1, "two": 2, "three": 3 }, "b": { "one": 1, "two": 2, "three": 3 } }
配合合并标签“<<”使用可以与任意数据进行合并,可以把这套操作,类比为面向对象语言中的继承。如下代码:
# YAML human: &base # 添加名为 base 的锚点 body: 1 hair: 999 singer: <<: *base # 引用 base 锚点,实例化时会自动展开 skill: sing # 添加额外的属性 programer: <<: *base # 引用 base 锚点,实例化时会自动展开 hair: 6 # 覆写 base 中的属性 skill: code # 添加额外的属性
// JSON { "human": { "body": 1, "hair": 999 }, "singer": { "body": 1, "hair": 999, "skill": "sing" }, "programer": { "body": 1, "hair": 6, "skill": "code" } }
参考:https://zhuanlan.zhihu.com/p/145173920 https://www.jianshu.com/p/413576dc837e https://zhuanlan.zhihu.com/p/75067291 https://ruanyifeng.com/blog/2016/07/yaml.html
测试 yaml 转 json:https://www.lddgo.net/convert/yaml-to-json
二、C# 读取 YAML 配置文件示例
知道了 YAML 的特点和语法,下面就来上马试试看吧。
2.1 安装必要的动态库 YamlDotNet
首先通过 NuGet 安装依赖:YamlDotNet,这个动态库是比较专门为 C# 操作 YAML 定制的,官方支持非常好。

2.2 YAML 示例文件
如下文件中,一个主节点中包含两个子节点:
assetBundles: # 主节点 - name: myname # 子节点-1 size: 123 variant: '' version: 1 md5: sdhbuuhkhekghddfgkshjgn dependencies: [] local: false assets: - Assets1/Birthday_FUMSIAMO.png - Assets1/Partner_lock.png - Assets1/shop_01.png - name: myname2 # 子节点-2 size: 1232 variant: '' version: 2 md5: sdhbuuhkhekghddfgkshjgn dependencies: ["1","2"] local: false assets: - Assets2/Birthday_FUMSIAMO.png - Assets2/Partner_lock.png - Assets2/shop_01.png
2.2 实际的操作代码
2.2.1 先看测试代码和测试结果
// 必要的引用 using System; using System.Text; using System.Collections.Generic; using System.IO; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions;
// 测试一下 【读取、修改、保存】 static void Main(string[] args) { var serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); var deserializer = new DeserializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); var ymlFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "asset_table.yml"); if (File.Exists(ymlFile)) { var ymlContent = File.ReadAllText(ymlFile); // 读取 yaml 文件内容 var buildConfig = deserializer.Deserialize<BuildConfigFile>(ymlContent); // 序列化 if (buildConfig != null) { foreach (var item in buildConfig.assetBundles) { // 获取配置内容并修改 if(item.name== "myname") item.name = "myname_new"; else if(item.name=="myname2") item.name = "myname2_new"; } // 序列化成新的 yaml 文本并保存 var newYamlContent = serializer.Serialize(buildConfig); File.WriteAllText(ymlFile, newYamlContent); } } }
测试结果:
注意:修改后的文件存在调试文件夹中:binDebugnet7.0asset_table.yml,在项目中的 asset_table.yml 文件依然是没有变化。


2.2.2 根据示例 YAML 文件输出基本数据模型
public class BuildConfigFile { public class AssetBundleItem { [YamlMember(Alias = "name")] public string name { get; set; } [YamlMember(Alias = "size")] public long size { get; set; } [YamlMember(Alias = "variant")] public string variant { get; set; } [YamlMember(Alias = "version")] public long version { get; set; } [YamlMember(Alias = "md5")] public string md5 { get; set; } [YamlMember(Alias = "md5bytes")] public string md5bytes { get; set; } [YamlMember(Alias = "dependencies")] public string[] dependencies { get; set; } [YamlMember(Alias = "local")] public bool local { get; set; } [YamlMember(Alias = "assets")] public string[] assets { get; set; } } [YamlMember(Alias = "assetBundles")] public List<AssetBundleItem> assetBundles { get; set; } }
2.2.3 最后是压轴的 YAML 操作类
public static class YamlHelper { private static ISerializer _serializer; private static IDeserializer _deserializer; static YamlHelper() { _serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); _deserializer = new DeserializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); } public static string Serialize(object target) { return _serializer.Serialize(target); } public static void SerializeToFile(object target, string filePath) { var content = Serialize(target); File.WriteAllText(filePath, content, Encoding.UTF8); } public static T Deserialize<T>(string yaml) { return _deserializer.Deserialize<T>(yaml); } public static T DeserializeFromFile<T>(string filePath) { var yaml = File.ReadAllText(filePath, Encoding.UTF8); return Deserialize<T>(yaml); } }
2.3 遇到的一个报错“Property 'assetBundles' not found on type 'TimerDispose.BuildConfigFile'.”
第一次运行没问题,但重复运行程序时,就会报出这个错误:

原因:是由于生成新的 YAML 文件中主节点由原本的 asset_bundles 更新成 assetBundles。数据模型中设置为 Alias = "asset_bundles",因此无法读取成功。
因此,这个报错的主要意思就是,根据设定好的数据模型,因字段对应不上而识别失败。