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:

    // 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<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);
        }
    }
}

测试结果:

注意:修改后的文件存在调试文件夹中:\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<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",因此无法读取成功。

因此,这个报错的主要意思就是,根据设定好的数据模型,因字段对应不上而识别失败。

参考:https://blog.csdn.net/rjcql/article/details/134341930

相关推荐
方安乐18 小时前
命理学研究
其他
闪闪发亮的小星星3 天前
欧拉角的定义
其他
探序基因3 天前
R语言读取h5格式的文件
其他
t057773 天前
立足通用航空生态 德意志飞机与COMTRONIC开启D328eco合作新篇章
其他
方见华Richard4 天前
递归对抗引擎RAE V4.0(AGI自主进化版)
经验分享·笔记·其他·交互·学习方法
老陈头聊SEO4 天前
长尾关键词在SEO优化中的应用及其对流量提升的影响分析
其他·搜索引擎·seo优化
晚霞的不甘5 天前
Flutter for OpenHarmony:迈向专业:购物APP的架构演进与未来蓝图
其他·flutter·架构·fiddler·前端框架·harmonyos
t057776 天前
精工美学新标杆!VELO ANGEL RIDE 坐垫尽显骑行美学
其他
t057776 天前
解锁跑骑肌肉密码!维乐 Angel 3D 坐垫让训练更高效
其他
老陈头聊SEO7 天前
生成引擎优化(GEO)在提升内容价值与搜索引擎排名中的关键作用
其他·搜索引擎·seo优化