Wjybxx.Dson 2.2.0-rc1

Suggested Alternatives

Wjybxx.Dson.Core 2.2.0

Additional Details

The package has been merged into commons

This is a prerelease version of Wjybxx.Dson.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Wjybxx.Dson --version 2.2.0-rc1                
NuGet\Install-Package Wjybxx.Dson -Version 2.2.0-rc1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Wjybxx.Dson" Version="2.2.0-rc1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Wjybxx.Dson --version 2.2.0-rc1                
#r "nuget: Wjybxx.Dson, 2.2.0-rc1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Wjybxx.Dson as a Cake Addin
#addin nuget:?package=Wjybxx.Dson&version=2.2.0-rc1&prerelease

// Install Wjybxx.Dson as a Cake Tool
#tool nuget:?package=Wjybxx.Dson&version=2.2.0-rc1&prerelease                

Dson(DataScript Object Notation)

Dson是一个有点奇怪的配置文件格式,但这也许会成为流行的配置文件格式。

Dson同时设计了二进制和文本格式,二进制用于网络传输,文本格式用于配置文件;另外,还提供了数字表示字段的高压缩率二进制版本。
Dson核心包不提供Dson到对象的编解码实现,只包含Dson的二进制流和文本解析实现;Dson支持复杂的数据结构,同时还设计了对象头,因此实现自己的Codec是很容易的。

Dson最初是为序列化而创建的,但我们在这里只讨论文本格式,二进制格式详见 Dson二进制流, 另外,Dson的一些设计可能与你期望的不同,为避免频繁提问,我将一些设计理由进行了整理,见 Dson设计过程中的一些反思
另外,我将一些特殊的测试用例或者说模板整理了一下,见 Dson测试用例

Dson的目标

  1. 简单
  2. 精确
  3. 易于编写和阅读
  4. 易于维护(修改)
  5. 易于解析和输出
  6. 易于扩展
  7. 高性能

Dson的特性

  1. 支持注释
  2. 支持指定值类型,精确解析
  3. 支持行合并,长内容行随意切割
  4. 支持纯文本输入,所见即所得,真正摆脱转义字符 - 也支持混合输入模式
  5. 引号不是必须的
  6. 支持对象头,可自定义对象头内容
  7. 支持读取Json

文本示例(标准)

以下文本为手写文本,用到的特殊标签包括:文本块"""、 单行纯文本sL
(文本见测试用例DsonTextReaderTest2.java)

   // 以下是一个简单的DsonObject示例
   {@{clsName:MyClassInfo, guid :10001, flags: 0}
     name : wjybxx,
     age: 28,
     pos :{@{Vector3} x: 0, y: 0, z: 0},
     address: [
       beijing,
       chengdu
     ],
     intro:
     """
       我是wjybxx,是一个游戏开发者,Dson是我设计的文档型数据表达法,你可以通过github联系到我。
       thanks
     """
     , url: @sL https://www.github.com/hl845740757
     , time: {@dt date: 2023-06-17, time: 18:37:00, millis: 100, offset: +08:00}
   }

Dson类型系统

声明类型

我们可以通过@label声明一个元素的类型,一般是修饰下一个元素,元素可以是 key,也可以是value,取决于上下文。
普通value类型,可在value的前方通过@声明类型;object和array则在{}和[]内声明其类型,且声明时和外层括号之间无空格。

ps: 当@作用于普通值类型和内置简单结构体时,我们称@声明的是其类型;当@作用于object和array时,我们称@声明的是对象的头信息。

内建类型

Dson支持的值类型和内置结构体包括:

标签 类型 枚举 含义 内置结构体 格式或示例
i int32 1 32位整型 @i 123 <br> @i 0xFF
L int64 2 64位长整型,大写L @L 123 <br> @L 0xFF
f float 3 32位浮点数 @f 1.0
d double 4 64位浮点数 @d 1.5 <br> 1.5
b bool 5 bool值 @b true <br> true <br/> @b 1
s string 6 字符串 "10" <br> abc
N null 7 null,大写N @N null <br> null
bin binary 8 二进制 @bin "FFFE" <br> @bin ""
ptr pointer 11 指针 {<br> string namespace;<br> string localId;<br> int32 type; <br> int32 policy; <br>} 格式为单值 '@ptr localId' 格式或 object格式 <br/> @ptr 1001 <br> {@ptr ns: wjybxx, localId: 10001, type: 0}
lptr lite pointer 12 轻量指针 {<br> string namespace;<br> int64 localId;<br> int32 type; <br> int32 policy; <br>} 格式为单值 '@lptr localId' 格式或 object格式 <br/> @lptr 1001 <br> {@lptr ns: wjybxx, localId: 10001, type: 0}
dt datetime 13 日期时间 { <br> int64 seconds; <br> int32 nanos;<br> int32 offset;<br> int32 enables; <br> } 单值或object结构<br/> @dt 2023-06-17T18:37:00 <br/>{@dt date: 2023-06-17, time: 18:37:00, offset: +08:00, millis: 100}
ts timestamp 14 时间戳 { <br> int64 seconds; <br> int32 nanos;<br> } 单值或object结构<br/> @ts 1715659200 <br/>{@ts seconds: 1715659200, nanos: 100}
header 29 对象头 对象形式: @{clsName: Vector3 } <br/> 简写形式: @{Vector3}
array 30 数组 [ 1, 2, 3, 4, 5 ]
object 31 对象/结构体 { name: wjybxx, age: 28 }

特殊类型

特殊类型不是真正的类型,而是对类型的修饰,是语法糖,用以简化书写。

标签 类型 含义 格式或示例
sL string line 单行纯文本 <br/>1. 单行有效,行尾结束 <br>2. 不会对字符进行转义,所见即所得<br/>3. 用于简化书写 { <br/>url: @sL https://github.com/hl845740757<br/>}

Dson规范

全局规范

  1. Dson是基于行解析的,换行符只支持\n\r\n
  2. //双斜杠表示注释,单行有效。
  3. 标签与被修饰的元素之间必须通过一个空格或换行分隔
  4. 顶层对象必须是Object/Array/Header,你不可以直接声明一个基础值类型,顶层对象之间可以不使用逗号分隔。
  5. {@clsName }[@clsName ] 表示内置结构体,{@{clsName} }[@{clsName} ] 表示用户自定义结构体。
  6. Dson大小写敏感。
  7. Json是有效的Dson输入。

ps: 我去除了顶层不能是header的限制,因此可以用顶层的header来表达文件头,用作其它目的都是不好的。

Number系列

  1. 简单数字格式以外的任何格式,都必须声明类型标签(含下划线时也是需要的)
  2. NaN、Infinity、-Infinity的支持取决于编程语言 —— 我现在不清楚是否有语言不支持
  3. int32和int64支持16进制和2进制,16进制以0x或0X开头,2进制以0b或0B开头;支持负号。
  4. 浮点数支持科学计数法。
  5. 整数和浮点数都支持下划线分割,不限制分割方式,但建议3个一组。
    1. 首尾不可以是下划线
    2. 不可以连续多个,下划线也不可以和小数点连续。
  6. 如无特殊说明,内置结构中的number都遵循这里的规范
   {
     value1: 10001,
     value2: 1.05,
     value3: @i 0xFF,
     value4: @i 0b10010001,
     value5: @i 100_000_000,
     value6: @d 1.05E-15,
     value7: @d Infinity,
     value8: @d NaN,
     value9: @i -0xFF,
     value10: @i -0b10010001,
     value11: @d -1.05E-15,
   }

bool值

  1. 未声明类型的情况下,只支持 true和false 两个值,且大小写严格;
  2. 声明类型的情况下,支持 0和1 输入; 1表示true,0表示false;
  3. 声明类型的情况下,拼写错误将引发错误 -- null同理。
   {value1: true, value2: false, value3: @b 1, value4: @b 0}
   # 拼写错误将引发异常
   {value1: @b ture, value2: @N nul} 

字符串(s、sL)

  1. 可以使用@s或双引号强调值是一个字符串类型 —— 更推荐使用双引号强调。
  2. 无引号字符串遇见换行符、不安全字符时将判定为结束;安全字符较多,我们给出不安全字符集。
  3. 存在一些特殊含义的字符串,用户最好总是使用双引号强调是字符串,否则可能发生兼容性问题。
引号字符串

可以使用双引号强调值为字符串类型,双引号会执行转义。

  1. 使用双引号时,反斜杠\ 是转义字符,解析时将对引号进行转义,因此内容中出现双引号时需要转义;
  2. 使用双引号时,换行需要显式使用\n
  3. 双引号字符串换行不可缩进。
单行字符串

可以使用@sL表示接下来是一个以换行符结尾的字符串,适合较短的字面量输入输出。

  1. 标签后只有第一个空格是缩进,直到换行符。
  2. 不对内容进行转义。
  3. 请尽量避免末尾空白字符,以免所见和所得不同。
    {
      url:  @sL https://github.com/hl845740757
    }
简单文本块

简单文本块通过三个引号"""来匹配。规则如下:

  1. 开始和结束的三个"""必须位于新行,这两行不属于内容行。
  2. 文本块开始的"""确定整体的缩进,此后的每一行至少具备相同的缩进。
  3. 简单文本块不支持转义,因此内容行的开始应避免出现"""
  4. 简单文本框不支持行的拆分和合并。
  5. 简单文本框不支持注释。

PS: 可类比markdown的代码块格式。

  // 第一个引号确定缩进,此后的每一行至少具备相同的缩进;不执行转义,保留原始文本
  """
    我是wjybxx,是一个游戏开发者,Dson是我设计的文档型数据表达法,
  你可以通过github联系到我。
    thanks
  """
Dson文本块

简单的文本块仍然存在引号的转义和单行过长问题,因此Dson还设计了基于行首分割的文本块。 Dson风格的文本块通过@"""来匹配,规则如下:

  1. 起始的三引号@"""后必须立即换行。
  2. 内容行通过行首标记内容开始,只有第一个空格是缩进,行首包括 @-@|@^,详情见下表。
  3. 内容行不支持行尾注释;非内容行可以是注释。

PS: 虽然Dson文本块不要求行首@对齐,也不要求开始和结束引号对齐,但建议总是对齐,以方便阅读。

  // 相对简单文本块,使用 @""" 开始,结束符相同
  @"""
  @-   我是wjybxx,是一个游戏开发者,Dson是我设计的文档型数据表达法,
  @- 你可以通过github联系到我。
  @|   thanks
  @"""
文本块行首
标签 备注 类型 含义 格式或示例
- 连字符 append 合并行,上一行的换行符无效 @- 这里续写上一行
| 竖线 append line 普通行,上一行换行符有效 @| 这是开启新的一行
^ 亦或 switch mode 模式切换,插入转义行;<br/>上一行换行符无效 @- 我想插入大量换行符<br/>@^ \n\n\n\n\n\n\n\n\n\n<br/>@- 接着书写
不安全字符
  1. 空白字符
  2. dson token字符集:花括号{}、方括号[]、引号"、逗号, 、冒号:、艾特@、斜杆 / 、反斜杠\
  3. 特殊含义字符串:"true", "false", "null", "undefine", "NaN", "Infinity", "-Infinity"
  4. 简单数字(整数和小数)
转义字符

转义字符与json基本一致,但 '/' 无需转义。

引号 反斜杠 退格(BS) 换页(FF) 换行(LF) 回车(CR) 水平制表(HT) unicode字符
\" \\ \b \f \n \r \t \u

注意:反斜杠\ 与要转义的字符必须连续,中途不可以换行。

二进制(bin)

  1. 配置格式限定 @bin "data"
  2. data部分使用 16进制 编码,需要使用双引号(考虑到数据可能很长,可能需要换行)
  3. 换行时不可缩进
   @bin "ABCD"
   @bin ""

指针(ptr)

  1. 指针支持两种范式 @ptr localId{@ptr localId: $localId, ns: $ns, type: $type, policy: $policy}
  2. @ptr localId 简写方式适用大多数情况,结构体用于复杂情况。
  3. ns是namespace的缩写,localId和namespace限定字符串,无特殊符号时可省略引号。
  4. Dson默认不解析指针,只存储为ptr结构,会提供根据localId解析的简单方法。
  5. type和policy的取值范围[0, 127],不支持特殊格式输入
  6. 字段拼写错误将引发异常。
   @ptr 10001
   {@ptr localId: wjybxx001, ns : global, type: 1 }
   {@ptr localId: wjybxx001, ns : global, type: 1, policy: 1}

PS:对于配置文件,指针的最大作用是复用和减少嵌套。

轻量指针(lptr)

轻量指针(lptr)是指针(ptr)的特化版,它们的结构体和语法基本一致,唯一的区别就是localIdint64类型, int64类型的localId的适用性不如字符串的localId广,但在特定的领域下可以极大的减少指针带来的开销。

  1. lptr支持两种范式 @lptr localId{@lptr localId: $localId, ns: $ns, type: $type, policy: $policy}
  2. @lptr localId 简写方式适用大多数情况,结构体用于复杂情况。
  3. ns是namespace的缩写,localId和namespace限定字符串,无特殊符号时可省略引号。
  4. Dson默认不解析指针,只存储为ptr结构。
  5. type和policy的取值范围[0, 127],不支持特殊格式输入
  6. 字段拼写错误将引发异常。
   @lptr 10001
   {@lptr localId: 10001, ns : global, type: 1 }
   {@lptr localId: 10001, ns : global, type: 1, policy: 1}

日期时间(dt)

  1. dt支持两种范式 @dt datetime {@dt date: yyyy-MM-dd, time: HH:mm:ss, offset: ±HH:mm:ss, millis: $millis, nanos: $nanos}
  2. @dt datetime 简写方式用于一般情况,datetime为 "yyyy-MM-ddTHH:mm:ss"格式,即ISO8601格式
  3. date 部分为 "yyyy-MM-dd" 格式。
  4. time 部分为 "HH:mm:ss" 格式,不可省略秒部分。
  5. offset 部分为 "Z", "±H", "±HH", "±HH:mm" 或 "±HH:mm:ss" 格式,不可以省略正负号。
  6. millis 表示输入毫秒转纳秒 —— 简化书写。
  7. nanos 表示直接输入纳秒,millis 和 nanos通常只应该出现一个。
  8. nanos 属于DateTime中的Time部分,只有输入time的情况下才可输入 nanos 或 millis。

    @dt 2023-06-17T18:37:00
    {@dt date: 2023-06-17, time: 18:37:00}
    {@dt date: 2023-06-17, time: 18:37:00, offset: +8}
    {@dt date: 2023-06-17, time: 18:37:00, offset: +08:00, millis: 100}
    {@dt date: 2023-06-17, time: 18:37:00, offset: +08:00, nanos: 100_000_000}

关于内置结构体的说明

  1. seconds 是纪元时间的秒时间(datetime)
  2. nanos 是时间戳的纳秒部分
  3. offset 是时区的秒数
  4. enables 记录了用户输入了哪些部分,由掩码构成,后4个比特位有效。
    1. date 的掩码是 0001
    2. time 的掩码是 0010
    3. offset 的掩码是 0100

时间戳(ts)

  1. dt支持两种范式 @ts seconds{@ts seconds: $seconds, millis: $millis, nanos: $nanos}
  2. @ts seconds 简写方式用于一般情况,结构体用于复杂情况。
  3. @ts seconds 支持毫秒时间戳,以ms结尾表示毫秒时间戳。
  4. millis 表示输入毫秒转纳秒 —— 简化书写。
  5. nanos 表示直接输入纳秒,millis 和 nanos通常只应该出现一个。
   @ts 1715659200
   @ts 1715659200100ms // ms结尾表示毫秒时间戳   
   {@ts seconds: 1715659200, millis: 100}

object

  1. 键值对之间通过 ',' (英文逗号) 分隔。
  2. key和value之间通过 ':' (英文冒号)分隔,冒号两边空格分隔key和value不是必须的,但冒号和value之间建议输入空格。
  3. key为字符串类型,适用字符串规范,无特殊字符时可省略双引号。
  4. @{} 用于声明对象的header信息 -- 新版本不再要求’@‘必须与'{'紧邻。
  5. 禁止末尾出现逗号,以避免奇怪语义

array

  1. value之间通过 ',' (英文逗号) 分隔。
  2. @{} 用于声明数组的header信息 -- 新版本不再要求’@‘必须与'['紧邻。
  3. 禁止允许末尾出现逗号,以避免奇怪语义
  1. header支持两种范式,"@{clsName}" 和 "@{k1:v1, k2:v2}"。
  2. "@{clsName}" 是 @{clsName: $clsName}的语法糖,用于一般情况下简化书写。
  3. clsName为字符串类型,适用字符串规范,无特殊字符时可不加引号。
  4. header结构体不可以再内嵌header
  5. header没有默认结构体,以允许用户自行扩展。

以下是object声明header的示例

    // 不声明header
    { x: 0, y: 0, z: 0 }
    // 只声明ClassName
    {@{Vector3} x: 0, y: 0, z: 0 }
    // 声明复杂的header
    {@{clsName: Vector3, localId: 321123} x: 1, y: 1, z: 1} 

${clsName}特殊支持

在Dson2.1中,我们对@{clsName}做了优化,在无引号字符串模式下,可通过}匹配结束
这个优化来源于泛型的序列化和反序列化需求,泛型类的clsName包含[]这俩特殊字符, 这导致我们的clsName常常需要使用双引号"包起来,为简化大多数情况下的文本编写和提高可读性,我们对其做特殊的语法支持。

   # 以下两种的方式等价 
   [@{List[V3]}
     {x: 1, y: 1.5, z: 1},
     {x: 2, y: 2.5, z: 2}
   ]
   [@{"List[V3]"}
     {x: 1, y: 1.5, z: 1},
     {x: 2, y: 2.5, z: 2}
   ]
Header特殊属性依赖

虽然header没有默认结构体,但我们还是依赖了一些属性,以支持一些基础功能。因此,如果用户扩展header,使用了以下属性名,请确保类型一致,否则可能引发兼容性问题。

属性名 类型 含义 备注
clsName string ClassName的缩写,表达当前对象的类型 可包括内置基础值和内置结构体
localId string/int64/int32 对象本地id 用于支持默认的引用解析

ps:

  1. keyClsName和compClsName用于非泛型情况下的类型自解释 —— 用于跨语言序列化。
  2. 如果语言支持泛型,可以只使用clsName记录类型的完整信息 —— 用于单语言序列化。
  3. clsName不可以包含冒号:和大括号{},

无类型value解析规则

  1. 首先去掉value两端的空白字符
  2. 如果value首字符为 { ,则按照object解析
  3. 如果value首字符为 [ ,则按照array解析
  4. 如果value首字符为 " ,则按照字符串解析
  5. 如果value为 true 或 false ,则解析为bool类型
  6. 如果value为 null,则解析为null
  7. 如果value匹配整数或浮点数(特殊格式不匹配),则固定解析为double类型。
  8. 其它情况下默认解析为String

一些好的实践

  1. 顶级对象之间通过空内容行分离
  2. key尽量遵守一般编程语言变量命名规范,仅使用数字字母和下划线,且非数字开头,以避免引号;同时key应当保持简短,避免换行。
  3. ClassName尽量不包含特殊字符,使得ClassName总是可以无引号表达。可考虑通过点号'.'或下划线'_'分隔。
  4. Object和Array的header信息@{}总是与\[{紧邻。
  5. header的定义要保持简单和简短,避免增加额外的复杂度。
  6. 字符串尽量使用无引号模式或单行纯文本,手写转义是麻烦易出错的工作。
  7. 文本块总是通过行首左对齐。
  8. 一旦你想以某种特殊形式输入一个值,都应该显式声明这个值的类型
  9. 数字避免使用8进制,浮点数慎用16进制

PS:内置结构体的值类型都是确定的,因此可以不声明类型直接使用特殊方式输入。

高级概念

集合

在Dson标准中,允许一个文件中包含多段Dson文本,每段文本表达一个Object或Array; 在这种情况下,我们将整个文件看做一个小型数据库,一个单表或单集合的数据库; 由于每段文本的结构可能不一致,这里我们选择NoSql概念下的集合。

因此,在Dson库中读写整个文件的接口都包含Collection,而读写文件中单个值的接口则不包含。

投影

投影(Projection)是指在解析Dson文本时,只选择性地返回某些字段,而不是返回整个Dson文档 —— 类似MongoDB中的投影。

目标

配置文件的常见需求之一便是:获取配置的某个部分。Dson既然要作为配置文件,自然要支持这个需求。
值得一提的是,Reader和Scanner为投影做了原生支持,会尽量避免解析不必要的字段(token),而不是先完整解析后再投影,因此是高效的。

语法规则
  1. $header 表示投影对象的header,header总是全量投影;header默认不返回,只有显式指定的情况下返回;
  2. value为1表示选择,为0表示排除;全为0时表示反选模式,否则只返回value为1的字段 -- header不计入。
  3. $all 用于选择object的所有字段,强制为反选字段模式;主要解决声明header的影响,也方便进入排除模式。
  4. 如果无法根据投影信息确定投影值的类型,将由真实的数据决定返回值类型 -- 可用于测试数据类型。
  5. $slice 表示数组范围投影,对数组进行细粒度投影时必须声明$slice,否则返回空数组。
  6. $slice skip 表示跳过skip个元素,截取剩余部分;兼容 $slice [skip]输入;
  7. $slice [skip, count] 表示跳过skip个元素,截取指定个数的元素部分;
  8. $elem 表示数组元素进行投影。
  9. Object的投影为Object,Array的投影为Array。
  10. 点号'.'默认不是路径分隔符,需要快捷语法时需要用户自行定义。
语法示例

{
   name: 1,
   age: 0, // 不返回age字段
   pos: {
      $header: 1, // 返回pos的header
      $all: 1, // 返回pos的全部字段 -- 可表示这是一个object映射
      z: 0 // 排除z
   },
   arr1: {
      $slice: 1, // $slice 用于对数组进行切片投影
      $elem: { // $elem 用于对数组元素进行投影
         name: 1,
         pos: 1
      }
   },
   arr2: {$slice: 1}, // 跳过1个元素,选择剩余所有元素
   arr3: {$slice: [0, 5]}, // 选择数组的前5个元素、
   arr4: 1, // 返回arr4整个数组
   
   key1: {}, // 如果key1存在,则返回对应空Object/空Array。
   key2: {$header: 1}, // // 如果key2存在,返回的空Object或空Array将包含header。
}

PS:测试用例见ProjectionTest.java

多语言

作者本人是一个游戏开发者,熟悉JAVA、C#、LUA,后期会提供C#的Dson实现;其它语言可能只能依靠喜欢上Dson的小伙伴们提供。

  1. Java库使用指南

       # java maven坐标(最新可查看Mvn中央仓库)
       <groupId>cn.wjybxx.dson</groupId>
       <artifactId>dson-core</artifactId>
       <version>2.1.2</version>
    
  2. C#库使用指南

	nuget Wjybxx.Dson 2.1.2

与其他配置文件格式的比较

JSON和Bson

json存在的问题:

  1. 不支持注释。
    1. 不支持注释导致Json不适合做配置模板,虽然部分库支持注释,但并不是所有库都支持。
  2. 字符串不支持换行。
    1. 程序生成的json文件经常出现一行上千和上万个字符的情况。
    2. 手写json出现长文本时非常难受。
  3. 不支持文本模式,不支持所见即所得,要处理大量的转义字符。
    1. json字符串存在引号的时候,转义的问题更加明显。
  4. 总是要加引号,key要加,value要加,手写json的时候有点难受。
  5. 值的精确程度不够,json当初是为js脚本语言设计的。

bson存在的问题:

  1. bson最大的问题是兼容json,它包含了json的所有问题 —— 我觉得这是bson最大的失误。
  2. bson是为mongodb数据库设计的,其数据格式与类型与数据库强相关。
  3. bson的类型语法设计得有点复杂,因为它是基于标准的json格式设计出来。

YAML

我承认,初次看见YAML文件的时候感到很优美,比如unity生成的yaml文件。 但研究yaml的规则之后,我对于YAML的流行表示不解,我觉得yaml的流行是一种灾难。

虽然我讨厌写json,但我认为json设计的其实很优美,它非常简单,输入严格,你很容易学会使用它。
YAML看上去也很简单是不是?但YAML的简单只是表面的简单。

  1. 使用空格缩进来表示层级关系,这是YAML最大的优点和缺点。
    1. 使用不可见的空格缩进来表示层级关系,如果YAML是程序生成的,那很beautiful。
    2. 如果YAML是手工书写,并可能长期维护的,这就是灾难 —— 文件越大,嵌套越深,越痛苦。
    3. 个人认为缩进不应该影响正确性,这非常脆弱,多方面的脆弱。
  2. YAML换行处理治标不治本
    1. YAML提供了好多种符号来处理文本段换行问题,在我看来就是没有方案,因为各个都不彻底。
  3. YAML不易扩展,因为YAML的规则太复杂了。
    1. 写个无bug的Json解析工具可能一天,写个无bug的YAML解析工具呢?

YAML的设计哲学里忽略了一点,需要手工编写的文件,维护成本才是最重要的! 美观最多放第二。
其实json的可读性我觉得已经足够好了,yaml的排版虽然漂亮,但付出的代价太大 —— 我写个文件就是为了排版好看吗?

PS:

  1. 我经常怀疑YAML的设计者是不是程序员...
  2. 输入并不是越少越好,简化输入不能损害精确性

TOML

说实话,TOML我接触的很少,看了几个toml文件示例后,我觉得它太像properties文件的优化版了。 TOML的语法非常接近编程语言:

  1. 可通过点号 . 来获取属性和赋值;也支持了数据结构内联
  2. 字符串段落使用三个引号""" —— 现在编程语言流行这样,Java也支持;但我想彻底摆脱转义。
  3. 换行继续输入使用反斜杠+换行 —— 说起来我最开始也想过这个方案,使用shell的经验

不过,我的直觉是TOML并不适合编写复杂的配置文件,更不适合用作程序导出的文件格式,所以我不选择TOML。

PS:

  1. 支持数字使用下划线,来自TOML的提醒,我在写代码时经常用到。
  2. TOML语法也是比较独特的,Dson也是。
  3. 还有一个我比较陌生的配置文件格式 HOCON,但HOCON只适合程序员,因为太像脚本语言。

Dson呢?

Dson是一个看似复杂,实则简单的语言。
Dson主要解决的问题有三个:

  1. 单行过长问题 - 期望能换行继续输入
  2. 纯文本和转义问题 - 转义很影响可读性
  3. 序列化的精确性问题 - Dson最初是为序列化设计的

前两个问题,通过忽略换行符,使用\n和纯文本行标签得到了很好的解决。

序列化的精确性问题则通过对象头来解决;对象头是额外的数据区,不会污染正常的数据区; 我们通过在对象头中存储对象的类型信息,可以实现序列化的精确性,也可以实现一些其它的扩展功能。

特别鸣谢

这里感谢MongoDB的Bson团队,Bson源代码对于我实现Dson起到了很大的帮助作用。

个人公众号(游戏开发)

写代码的诗人

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.