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



    〇、简介

    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 编码
    - "\x0d\x0a is \r\n" # 使用双引号包裹时还支持 Hex 编码
    - 'He said: "Hello!"' # 单双引号支持嵌套"
    // 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!\""
    ]
    }

    用竖线符“ | ”来表示保留换行(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(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);
    }
    }
    }

    测试结果:

    注意:修改后的文件存在调试文件夹中:\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

  • 相关阅读:
    OKR vs. 任务:OKR与任务管理有何不同?
    Java继承初步讲解
    竞赛 基于深度学习的人脸专注度检测计算系统 - opencv python cnn
    界面控件Kendo UI for jQuery R3 2023 - 发布全新金字塔图表类型
    MySQL数据库期末考试试题及参考答案(04)
    vue在线查看pdf文件
    【华为机试真题 JAVA】工号不够用了怎么办-100
    关于 SAP ABAP SYSTEM_SHM_OPEN_CHANGE_LOCK 运行时错误的问题
    windows批处理 将当前路径添加到Windows的`PATH`环境变量中 %~dp0
    一站式企业协同研发云——云效
  • 原文地址:https://www.cnblogs.com/hnzhengfy/p/yaml_CS_example.html