〇、简介
YAML(Yet Another Markup Language)另一种标记语言。
YAML 是一种较为人性化的数据序列化语言,可以配合目前大多数编程语言使用。YAML 的语法比较简洁直观,特点是使用空格来表达层次结构,其最大优势在于数据结构方面的表达,所以 YAML 更多应用于编写配置文件,其文件一般以 .yml 为后缀。
特点:
- 易于阅读:YAML 使用缩进和比较简洁的语法来表示数据结构,使得它比许多其他数据格式更容易阅读和理解。
- 数据结构友好:YAML 天然支持标量(如字符串、整数、浮点数)、列表(数组)和映射(字典)等数据结构。
- 无类型标签:YAML 通过上下文来推断值的类型,不需要显式的类型标签。
- 可交互:YAML 可以在不同的编程语言之间进行交互,因为它有广泛的语言支持。
- 表达能力强:YAML 可以表示复杂的数据结构,并且可以通过锚点和别名来重用数据。
- 可伸缩性:YAML 可以很容易地扩展到新的数据类型,而不需要改变现有的解析器。
YAML 的使用场景包括但不限于:应用程序的配置、数据交换格式、文档撰写、自动化脚本、云计算和服务编排等等。
一、YAML 语法
1.1 基本语法
- 大小写敏感。
- 使用缩进表示层级关系。
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。
| |
| one: |
| two: 2 |
| three: |
| four: 4 |
| five: 5 |
| |
| "one": { |
| "two": 2, |
| "three": { |
| "four": 4, |
| "five": 5 |
| } |
| } |
每个文档均以“---”三个横杠开始,如果一个文件中仅一个文档,则可省略。
每个文档并不必须使用结束符“...”来表示结束,但是对于网络传输或者流来说,作为明确结束的符号,有利于软件处理。(例如,不需要知道流关闭就能知道文档结束)
1.2 数据结构与类型
1.2.1 对象 Mapping
标识以键值对(key: value)形式出现的数据。
在键和值中间加入标识,冒号+空格(: )。
用缩进表示层级关系
| |
| key: |
| child-key1: value1 |
| child-key2: value2 |
| |
| { |
| "key": { |
| "child-key1": "value1", |
| "child-key2": "value2", |
| } |
| } |
键值对之间用逗号+空格(, )分隔,类似 JSON。
| |
| key: { child-key1: value1, child-key2: value2 } |
| |
| { |
| "key": { |
| "child-key1": "value1", |
| "child-key2": "value2" |
| } |
| } |
当键是一个列表或键值表时,就需要使用本符号来标记。
| |
| [blue, reg, green]: Color |
| |
| ? - blue |
| - reg |
| - gree |
| : Color |
| |
| { |
| "blue,reg,gree": "Color" |
| } |
每个结构都可以嵌套组成复杂的表示结构。
| |
| div: |
| - border: {color: red, width: 2px} |
| - background: {color: green} |
| - padding: [0, 10px, 0, 10px] |
| |
| { |
| "div": [ |
| { |
| "border": { |
| "color": "red", |
| "width": "2px" |
| } |
| }, |
| { |
| "background": { |
| "color": "green" |
| } |
| }, |
| { |
| "padding": [0, "10px", 0, "10px"] |
| } |
| ] |
| } |
| |
| items: |
| - item: cpu |
| model: i3 |
| price: ¥800.00 |
| - item: HD |
| model: WD |
| price: ¥450.00 |
| |
| { |
| "items": [ |
| { |
| "item": "cpu", |
| "model": "i3", |
| "price": "¥800.00" |
| }, |
| { |
| "item": "HD", |
| "model": "WD", |
| "price": "¥450.00" |
| } |
| ] |
| } |
1.2.2 数组 Sequence
| |
| values: |
| - value1 |
| - value2 |
| - value3 |
| |
| values: [value1, value2, value3] |
| |
| { |
| "values": [ |
| "value1", |
| "value2", |
| "value3" |
| ] |
| } |
| |
| values: |
| - |
| - value1 |
| - value2 |
| - |
| - value3 |
| - value4 |
| |
| { |
| "values": [ |
| [ |
| "value1", |
| "value2" |
| ], |
| [ |
| "value3", |
| "value4" |
| ] |
| ] |
| } |
| |
| - [blue, red, green] |
| - [Age, Bag] |
| - site: {osc:www.oschina.net, baidu: www.baidu.com} |
| |
| [ |
| [ |
| "blue", |
| "red", |
| "green" |
| ], |
| [ |
| "Age", |
| "Bag" |
| ], |
| { |
| "site": { |
| "osc:www.oschina.net": null, |
| "baidu": "www.baidu.com" |
| } |
| } |
| ] |
| |
| languages: |
| - Ruby |
| - Perl |
| - Python |
| websites: |
| YAML: yaml.org |
| Ruby: ruby-lang.org |
| Python: python.org |
| Perl: use.perl.org |
| |
| { |
| "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、类型强制转换等。
字符串是最常见,也是最复杂的一种数据类型。
字符串一般不需要用引号包裹,但是如果字符串中使用了反斜杠“\”开头的转义字符就必须使用引号包裹。
| |
| strings: |
| - Hello without quote |
| - Hello |
| world |
| - 'Hello with single quotes' |
| - "Hello with double quotes" |
| - "I am fine. \u263A" |
| - "\x0d\x0a is \r\n" |
| - 'He said: "Hello!"' |
| |
| { |
| "strings": [ |
| "Hello without quote", |
| "Hello world", |
| "Hello with single quotes", |
| "Hello with double quotes", |
| "I am fine. ☺", |
| "\r\n is \r\n", |
| "He said: \"Hello!\"" |
| ] |
| } |
用竖线符“ | ”来表示保留换行(Newlines preserved)。
每行的前边缩进和后边的空白会被去掉,而额外的缩进和行后的空格会被保留。
| |
| lines: | |
| 我是第一行 |
| 我是第二行 |
| 我是吴彦祖 |
| 我是第四行 |
| 我是第五行 |
| |
| { |
| "lines": "我是第一行 \n我是第二行\n 我是吴彦祖\n 我是第四行\n我是第五行\n" |
| } |
用右尖括号“ > ”来表示折叠换行(Newlines folded)。
只有空白行才会被识别为换行,原来的换行符都会被转换成空格。最后也会以换行符结束。
| |
| lines: > |
| 我是第一行 |
| 我也是第一行 |
| 我仍是第一行 |
| 我依旧是第一行 |
| |
| 我是第二行 |
| 这么巧我也是第二行 |
| |
| { |
| "lines": "我是第一行 我也是第一行 我仍是第一行 我依旧是第一行\n我是第二行 这么巧我也是第二行\n" |
| } |
经测试,只有全部大写、全部小写、首字母大写这三种情况,可以自动识别为布尔值。其他情况均转成字符串,如下:
| |
| boolean: |
| - true |
| - True |
| - TRUE |
| - TRue |
| - false |
| - False |
| - FALSE |
| - FAlse |
| |
| { |
| "boolean": [ |
| true, |
| true, |
| true, |
| "TRue", |
| false, |
| false, |
| false, |
| "FAlse" |
| ] |
| } |
YAML 允许二进制的整数,但前边需要带上标识:‘0b’。
- 浮点数(Floating-point、float)
允许使用科学计数法,如下代码:
| |
| float: |
| - 3.14 |
| - 6.8523015e+5 |
| |
| { |
| "float": [ |
| 3.14, |
| 685230.15 |
| ] |
| } |
| |
| nulls: |
| - null |
| - Null |
| - ~ |
| - |
| |
| { |
| "nulls": [ |
| null, |
| null, |
| null, |
| null |
| ] |
| } |
没有 +8 小时的标记时,默认就是协调世界时(UTC),也就是标准时间,转换成 JSON 都是按照协调世界时的格式,如下代码:
| |
| dates: |
| - 2024-03-05 |
| - 2024-03-05T20:00:00 |
| - 2024-03-05T20:00:00+08:00 |
| - 2024-03-05T20:00:00.10+08:00 |
| - 2024-03-05 20:00:00.10 +8 |
| |
| { |
| "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 支持使用严格类型标签:“!!”(格式:双感叹号+目标类型),来强制转换类型,如下代码:
| |
| strings_convert: |
| - !!float '666' |
| - '666' |
| - !!str 666 |
| - !!str 666.66 |
| - !!str true |
| - !!bool 'true' |
| |
| { |
| "strings_convert": [ |
| 666, |
| "666", |
| "666", |
| "666.66", |
| "true", |
| true |
| ] |
| } |
1.3 数据重用和合并(&、*、<<)
为了保持内容的简洁,避免过多重复的定义,YAML 提供了由锚点标签“&”和引用标签“*”组成的语法,利用这套语法可以快速引用相同的一些数据。如下代码:
| |
| a: &anchor |
| one: 1 |
| two: 2 |
| three: 3 |
| b: *anchor |
| |
| { |
| "a": { |
| "one": 1, |
| "two": 2, |
| "three": 3 |
| }, |
| "b": { |
| "one": 1, |
| "two": 2, |
| "three": 3 |
| } |
| } |
配合合并标签“<<”使用可以与任意数据进行合并,可以把这套操作,类比为面向对象语言中的继承。如下代码:
| |
| human: &base |
| body: 1 |
| hair: 999 |
| singer: |
| <<: *base |
| skill: sing |
| programer: |
| <<: *base |
| hair: 6 |
| skill: code |
| |
| { |
| "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 |
| 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 |
| 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); |
| var buildConfig = deserializer.Deserialize(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"; |
| } |
| |
| var newYamlContent = serializer.Serialize(buildConfig); |
| File.WriteAllText(ymlFile, newYamlContent); |
| } |
| } |
| } |
测试结果:
注意:修改后的文件存在调试文件夹中:\bin\Debug\net7.0\asset_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 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(yaml); |
| } |
| public static T DeserializeFromFile<T>(string filePath) |
| { |
| var yaml = File.ReadAllText(filePath, Encoding.UTF8); |
| return Deserialize(yaml); |
| } |
| } |
2.3 遇到的一个报错“Property 'assetBundles' not found on type 'TimerDispose.BuildConfigFile'.”
第一次运行没问题,但重复运行程序时,就会报出这个错误:
原因:是由于生成新的 YAML 文件中主节点由原本的 asset_bundles 更新成 assetBundles。数据模型中设置为 Alias = "asset_bundles",因此无法读取成功。
因此,这个报错的主要意思就是,根据设定好的数据模型,因字段对应不上而识别失败。
参考:https://blog.csdn.net/rjcql/article/details/134341930