爱七香菜 發表於 2024-3-7 20:11:00

YAML 语法简介与 C# 操作示例

<h2>〇、简介</h2>
<p>YAML(Yet Another Markup Language)另一种标记语言。</p>
<p>YAML 是一种较为人性化的数据序列化语言,可以配合目前大多数编程语言使用。YAML 的语法比较简洁直观,特点是<strong>使用空格来表达层次结构</strong>,其<strong>最大优势在于数据结构方面的表达</strong>,所以 YAML 更多应用于编写配置文件,其文件一般<strong>以 .yml 为后缀</strong>。</p>
<p>特点:</p>
<ul>
<li>易于阅读:YAML 使用<strong>缩进和比较简洁的语法</strong>来表示数据结构,使得它比许多其他数据格式更容易阅读和理解。</li>
<li>数据结构友好:YAML <strong>天然支持标量(如字符串、整数、浮点数)、列表(数组)和映射(字典)等数据结</strong>构。</li>
<li>无类型标签:YAML 通过上下文来推断值的类型,<strong>不需要显式的类型标签</strong>。</li>
<li>可交互:YAML 可以在不同的编程语言之间进行交互,因为它<strong>有广泛的语言支持</strong>。</li>
<li>表达能力强:YAML 可以表示复杂的数据结构,并且可以通过<strong>锚点和别名来重用数据</strong>。</li>
<li>可伸缩性:YAML 可以很容易地扩展到新的数据类型,而不需要改变现有的解析器。</li>
</ul>
<p>YAML 的使用场景包括但不限于:应用程序的配置、数据交换格式、文档撰写、自动化脚本、云计算和服务编排等等。</p>
<h2>一、YAML 语法</h2>
<h3>1.1 基本语法</h3>
<ul>
<li>大小写敏感。</li>
<li>使用缩进表示层级关系。</li>
<li>缩进时不允许使用Tab键,只允许使用空格。</li>
<li>缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。</li>
</ul>
<pre class="language-yaml highlighter-hljs"><code># YAML
one:
two: 2
three:
    four: 4
    five: 5</code></pre>
<pre class="language-json highlighter-hljs"><code>// 转成 JSON 后的格式
"one": {
"two": 2,
"three": {
    "four": 4,
    "five": 5
}
}</code></pre>
<ul>
<li>用 # 标识注释,且只能单行</li>
</ul>
<pre class="language-yaml highlighter-hljs"><code># 我是一行注释
# 我是另一行注释</code></pre>
<ul>
<li>&nbsp;一个 YAML 文件可以包含多个文档</li>
</ul>
<p>每个文档均以“---”三个横杠开始,如果一个文件中<strong>仅一个文档,则可省略</strong>。</p>
<p>每个文档并不必须使用结束符“...”来表示结束,但是<strong>对于网络传输或者流来说,作为明确结束的符号,有利于软件处理</strong>。(例如,不需要知道流关闭就能知道文档结束)</p>
<pre class="language-yaml highlighter-hljs"><code>---
# 这是第一份文档内容
one: 1
# 其他内容...
...

---
# 这是第二份文档内容
two: 2
# 其他内容...</code></pre>
<h3>1.2 数据结构与类型</h3>
<h4>1.2.1 对象 Mapping</h4>
<p>标识以键值对(key: value)形式出现的数据。</p>
<ul>
<li><strong>格式</strong></li>
</ul>
<p>在键和值中间加入标识,<strong>冒号+空格(: )</strong>。</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
key: value</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "key": "value"
}</code></pre>
<ul>
<li><strong>多层嵌套数据</strong></li>
</ul>
<p>用<strong>缩进</strong>表示层级关系</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
key:
child-key1: value1
child-key2: value2</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "key": {
      "child-key1": "value1",
      "child-key2": "value2",
    }
}</code></pre>
<ul>
<li><strong>用一对 {} 花括号包裹,表示一个键值表</strong></li>
</ul>
<p>键值对之间用<strong>逗号+空格(, )</strong>分隔,类似 JSON。</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
key: { child-key1: value1, child-key2: value2 }</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "key": {
      "child-key1": "value1",
      "child-key2": "value2"
    }
}</code></pre>
<ul>
<li><strong>问号+空格(? )表示复杂的键</strong></li>
</ul>
<p>当<strong>键是一个列表或键值表</strong>时,就需要使用本符号来标记。</p>
<pre class="language-yaml highlighter-hljs"><code># 使用一个列表作为键
: Color
# 等价于
? - blue
- reg
- gree
: Color</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "blue,reg,gree": "Color"
}</code></pre>
<ul>
<li><strong>多种组合表示</strong></li>
</ul>
<p>每个结构都可以嵌套组成复杂的表示结构。</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
div:
- border: {color: red, width: 2px}
- background: {color: green}
- padding: </code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "div": [
      {
            "border": {
                "color": "red",
                "width": "2px"
            }
      },
      {
            "background": {
                "color": "green"
            }
      },
      {
            "padding":
      }
    ]
}</code></pre>
<pre class="language-yaml highlighter-hljs"><code># YAML
items:
- item: cpu
    model: i3
    price: ¥800.00
- item: HD
    model: WD
    price: ¥450.00</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "items": [
      {
            "item": "cpu",
            "model": "i3",
            "price": "¥800.00"
      },
      {
            "item": "HD",
            "model": "WD",
            "price": "¥450.00"
      }
    ]
}</code></pre>
<h4>1.2.2 数组 Sequence</h4>
<ul>
<li><strong>横线+空格(- )开头的数据组成一个数组</strong></li>
</ul>
<pre class="language-yaml highlighter-hljs"><code># YAML 区块格式(Block Format)
values:
- value1
- value2
- value3
# YAML 内联格式(Inline Format)
values: </code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "values": [
      "value1",
      "value2",
      "value3"
    ]
}</code></pre>
<ul>
<li><strong>多维数组</strong></li>
</ul>
<pre class="language-yaml highlighter-hljs"><code># YAML
values:
-
    - value1
    - value2
-
    - value3
    - value4</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "values": [
      [
            "value1",
            "value2"
      ],
      [
            "value3",
            "value4"
      ]
    ]
}</code></pre>
<ul>
<li><strong>数组组合</strong></li>
</ul>
<pre class="language-yaml highlighter-hljs"><code># YAML
-    # 列表项本身也是一个列表
-
- site: {osc:www.oschina.net, baidu: www.baidu.com}# 这里是同 键值表 组合表示</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
[
    [
      "blue",
      "red",
      "green"
    ],
    [
      "Age",
      "Bag"
    ],
    {
      "site": {
            "osc:www.oschina.net": null,
            "baidu": "www.baidu.com"
      }
    }
]</code></pre>
<ul>
<li><strong>复合结构</strong></li>
</ul>
<pre class="language-yaml highlighter-hljs"><code># YAML
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "languages": [
      "Ruby",
      "Perl",
      "Python"
    ],
    "websites": {
      "YAML": "yaml.org",
      "Ruby": "ruby-lang.org",
      "Python": "python.org",
      "Perl": "use.perl.org"
    }
}</code></pre>
<h4>1.2.3 标量 Scalars 基本数据类型-str、bool、int、float、null、datetime...</h4>
<p>本章节包含以下部分简介:<strong>字符串 String、布尔值 boolean、整数 Integer、浮点数 Float、空 Null、日期时间 datetime、类型强制转换</strong>等。</p>
<ul>
<li><strong>字符串(string、str)</strong></li>
</ul>
<p>字符串是最常见,也是最复杂的一种数据类型。</p>
<p>字符串<strong>一般不需要用引号包裹</strong>,但是如果字符串中<strong>使用了反斜杠“\”开头的转义字符</strong>就必须使用引号包裹。</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
strings:
- Hello without quote # 不用引号包裹
- Hello
   world # 拆成多行后会自动在中间添加空格
- 'Hello with single quotes' # 单引号包裹
- "Hello with double quotes" # 双引号包裹
- "I am fine. \u263A" # 使用双引号包裹时支持 Unicode 编码
- "\x0d\x0a is \r\n" # 使用双引号包裹时还支持 Hex 编码
- 'He said: "Hello!"' # 单双引号支持嵌套"</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "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!\""
    ]
}</code></pre>
<p><strong>用竖线符“ | ”来表示保留换行(Newlines preserved)。</strong></p>
<p>每行的<strong>前边缩进</strong>和<strong>后边的空白</strong>会被去掉,而<strong>额外的缩进和行后的空格</strong>会被保留。</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
lines: |
我是第一行   
我是第二行
    我是吴彦祖
      我是第四行
我是第五行</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "lines": "我是第一行    \n我是第二行\n我是吴彦祖\n    我是第四行\n我是第五行\n"
}</code></pre>
<p><strong>用右尖括号“ &gt; ”来表示折叠换行(Newlines folded)。</strong></p>
<p>只有<strong>空白行</strong>才会被识别为换行,原来的换行符都会被转换成<strong>空格</strong>。最后也会以换行符结束。</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
lines: &gt;
我是第一行
我也是第一行
我仍是第一行
我依旧是第一行

我是第二行
这么巧我也是第二行</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "lines": "我是第一行 我也是第一行 我仍是第一行 我依旧是第一行\n我是第二行 这么巧我也是第二行\n"
}</code></pre>
<ul>
<li><strong>布尔值(Boolean、bool)</strong></li>
</ul>
<p>经测试,只有全部大写、全部小写、首字母大写这三种情况,可以自动识别为布尔值。其他情况均转成字符串,如下:</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
boolean:
- true
- True
- TRUE
- TRue
- false
- False
- FALSE
- FAlse</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "boolean": [
      true,
      true,
      true,
      "TRue",
      false,
      false,
      false,
      "FAlse"
    ]
}</code></pre>
<ul>
<li><strong>整数(Integer、int)</strong></li>
</ul>
<p>YAML 允许二进制的整数,但前边需要带上标识:‘0b’。</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
int:
- 666
- 0b0010_1110# 二进制表示</code></pre>
<pre class="language-json highlighter-hljs"><code>//JSON
{
    "int": [
      666,
      46
    ]
}</code></pre>
<ul>
<li><strong>浮点数(Floating-point、float)</strong></li>
</ul>
<p>允许使用科学计数法,如下代码:</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
float:
- 3.14
- 6.8523015e+5 # 使用科学计数法</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "float": [
      3.14,
      685230.15
    ]
}</code></pre>
<ul>
<li><strong>空(Null)</strong></li>
</ul>
<pre class="language-yaml highlighter-hljs"><code># YAML
nulls:
- null
- Null
- ~
-# 未指定值</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "nulls": [
      null,
      null,
      null,
      null
    ]
}</code></pre>
<ul>
<li><strong>日期时间(date、datetime)</strong></li>
</ul>
<p>没有 +8 小时的标记时,默认就是协调世界时(UTC),也就是标准时间,转换成 JSON 都是按照协调世界时的格式,如下代码:</p>
<pre class="language-yaml highlighter-hljs"><code># 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</code></pre>
<pre class="language-json highlighter-hljs"><code>// 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"
    ]
}</code></pre>
<ul>
<li><strong>类型转换(双叹号:!!)</strong></li>
</ul>
<p>YAML 支持使用严格类型标签:“!!”(格式:双感叹号+目标类型),来强制转换类型,如下代码:</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
strings_convert:
- !!float '666' # 字符串转浮点数
- '666'
- !!str 666 # 整数转为字符串
- !!str 666.66 # 浮点数转为字符串
- !!str true # 布尔值转为字符串
- !!bool 'true' # 字符串转布尔值</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "strings_convert": [
      666,
      "666",
      "666",
      "666.66",
      "true",
      true
    ]
}</code></pre>
<h3>1.3 数据重用和合并(&amp;、*、&lt;&lt;)</h3>
<p>为了保持内容的简洁,避免过多重复的定义,YAML 提供了由锚点标签“&amp;”和引用标签“*”组成的语法,利用这套语法可以快速引用相同的一些数据。如下代码:</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
a: &amp;anchor # 设置锚点
one: 1
two: 2
three: 3
b: *anchor # 引用锚点</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "a": {
      "one": 1,
      "two": 2,
      "three": 3
    },
    "b": {
      "one": 1,
      "two": 2,
      "three": 3
    }
}</code></pre>
<p>配合合并标签“&lt;&lt;”使用可以与任意数据进行合并,可以把这套操作,类比为面向对象语言中的继承。如下代码:</p>
<pre class="language-yaml highlighter-hljs"><code># YAML
human: &amp;base # 添加名为 base 的锚点
    body: 1
    hair: 999
singer:
    &lt;&lt;: *base # 引用 base 锚点,实例化时会自动展开
    skill: sing # 添加额外的属性
programer:
    &lt;&lt;: *base # 引用 base 锚点,实例化时会自动展开
    hair: 6 # 覆写 base 中的属性
    skill: code # 添加额外的属性</code></pre>
<pre class="language-json highlighter-hljs"><code>// JSON
{
    "human": {
      "body": 1,
      "hair": 999
    },
    "singer": {
      "body": 1,
      "hair": 999,
      "skill": "sing"
    },
    "programer": {
      "body": 1,
      "hair": 6,
      "skill": "code"
    }
}</code></pre>
<p><em><span style="font-size: 12px">参考:https://zhuanlan.zhihu.com/p/145173920 &nbsp;https://www.jianshu.com/p/413576dc837e &nbsp; https://zhuanlan.zhihu.com/p/75067291 &nbsp; https://ruanyifeng.com/blog/2016/07/yaml.html</span></em></p>
<p><em><span style="font-size: 12px">测试 yaml 转 json:https://www.lddgo.net/convert/yaml-to-json &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span></em></p>
<h2>二、C# 读取 YAML 配置文件示例</h2>
<p>知道了 YAML 的特点和语法,下面就来上马试试看吧。</p>
<h3>2.1 安装必要的动态库 YamlDotNet</h3>
<p>首先通过 NuGet 安装依赖:YamlDotNet,这个动态库是比较专门为 C# 操作 YAML 定制的,官方支持非常好。</p>
<p><img src="https://img2024.cnblogs.com/blog/1868241/202403/1868241-20240307192849708-1430556321.png"></p>
<h3>2.2 YAML 示例文件</h3>
<p>如下文件中,一个主节点中包含两个子节点:</p>
<pre class="language-yaml highlighter-hljs"><code>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</code></pre>
<h3>2.2 实际的操作代码</h3>
<h4>2.2.1 先看测试代码和测试结果</h4>
<pre class="language-csharp highlighter-hljs"><code>// 必要的引用
using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;</code></pre>
<pre class="language-csharp highlighter-hljs"><code>// 测试一下 【读取、修改、保存】
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&lt;BuildConfigFile&gt;(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);
      }
    }
}</code></pre>
<p>测试结果:</p>
<p><em><span style="font-size: 12px">注意:修改后的文件存在调试文件夹中:\bin\Debug\net7.0\asset_table.yml,在项目中的 asset_table.yml 文件依然是没有变化。</span></em></p>
<p><img src="https://img2024.cnblogs.com/blog/1868241/202403/1868241-20240307194439998-664700917.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/1868241/202403/1868241-20240307194838835-1780317164.png"></p>
<h4>2.2.2 根据示例 YAML 文件输出基本数据模型</h4>
<pre class="language-csharp highlighter-hljs"><code>public class BuildConfigFile
{
    public class AssetBundleItem
    {
      
      public string name { get; set; }

      
      public long size { get; set; }

      
      public string variant { get; set; }

      
      public long version { get; set; }

      
      public string md5 { get; set; }

      
      public string md5bytes { get; set; }

      
      public string[] dependencies { get; set; }

      
      public bool local { get; set; }

      
      public string[] assets { get; set; }
    }

   
    public List&lt;AssetBundleItem&gt; assetBundles { get; set; }
}</code></pre>
<h4>2.2.3 最后是压轴的 YAML 操作类</h4>
<pre class="language-csharp highlighter-hljs"><code>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&lt;T&gt;(string yaml)
    {
      return _deserializer.Deserialize&lt;T&gt;(yaml);
    }
    public static T DeserializeFromFile&lt;T&gt;(string filePath)
    {
      var yaml = File.ReadAllText(filePath, Encoding.UTF8);
      return Deserialize&lt;T&gt;(yaml);
    }
}</code></pre>
<h3>2.3 遇到的一个报错“Property 'assetBundles' not found on type 'TimerDispose.BuildConfigFile'.”</h3>
<p>第一次运行没问题,但重复运行程序时,就会报出这个错误:</p>
<p><img src="https://img2024.cnblogs.com/blog/1868241/202403/1868241-20240307195157938-1305022576.png"></p>
<p><strong>原因:</strong>是由于生成新的 YAML 文件中主节点由原本的 asset_bundles 更新成 assetBundles。数据模型中设置为 Alias = "asset_bundles",因此无法读取成功。</p>
<p><strong>因此,这个报错的主要意思就是,根据设定好的数据模型,因字段对应不上而识别失败。</strong></p>
<p><span style="font-size: 12px"><em>参考:https://blog.csdn.net/rjcql/article/details/134341930</em></span></p>

</div>
<div id="MySignature" role="contentinfo">
    <img src="https://images.cnblogs.com/cnblogs_com/blogs/683398/galleries/2417207/o_221441c4.png">
<p>本文来自博客园,作者:橙子家,欢迎微信扫码关注博主【橙子家czzj】,有任何疑问欢迎沟通,共同成长!</p>
<div><p style="float: left;">转载本文请注明原文链接:https://www.cnblogs.com/hnzhengfy/p/yaml_CS_example.html</p></div><br><br>
来源:https://www.cnblogs.com/hnzhengfy/p/yaml_CS_example.html
頁: [1]
查看完整版本: YAML 语法简介与 C# 操作示例