涵盖了JSON5中的基本类型
null- boolean:
true或false - number: 64位浮点数
- ✅ 十进制整数和小数
- ✅
0x十六进制,0o八进制,0b二进制 - ❌ 不支持以单个0开头的八进制数字,如0123e4, -0345,极易造成混淆,并且js严格模式下不支持
⚠️ 不支持大写形式的0X,0O,0B,因为大写O极易与0混淆- ✅ 科学计数法
- ✅
NaN,Infinity,-Infinity - ✅ .01和10.
- ✅ 可以有前导的
+
- string: 字符串
- ✅ 单引号
- ✅ 双引号
- ✅ 不转义的Unicode字符
- ✅
\uXXXXUnicode字符转义 - ✅
\xXX十六进制转义 - ✅
\b \f \n \r \t \v \0 \' \" ⚠️ js支持\u{XXXX}形式的Unicode字符转义,但这和\uXXXX作用完全相同并且更长,故被排除以简化语法- ❌ 不支持转义行尾换行符,从而让字符串跨域多行。TASON主要面向数据传输而不是配置文件,在跨平台情况下检查换行符采用的是CRLF还是LF十分困难
⚠️ 不支持转义/,在大部分语言中正斜杠都无需转义。即使作为参数传给内置的RegExp标量类型,也无需转义
- array: 数组
- ✅ 可以有尾随逗号
- ✅ 支持各种可迭代对象。例如在JavaScript中指
Iterable<T>, C#中指IEnumerable<T>
- object: 对象字面量
- ✅ 可以有尾随逗号
- ✅ 属性名可以是符合
[a-zA-Z_][a-zA-Z0-9_]*的标识符 - ✅ 属性名可以是字符串
⚠️ 不支持以$开头的标识符,因为很多语言如C#的标识符不能以$开头- ❌ 不支持将其他合法的Unicode字符(包括汉字)作为标识符,容易造成混淆,并且非字母数字的标识符很少作为属性名
- 注释
- ✅ 单行注释
- ✅ 多行注释
- 空白字符
- ✅ 空格
,\r,\n,\t - ❌
\b,\v,\f,\0: 不应该出现在代码中 - ❌ 行分隔符
\u2028, 段落分隔符\u2029: 臭名昭著 ⚠️ 不支持零宽度空格\u200B: 该字符极易在各种软件特别是URL末尾中产生,且无法看见,从而造成难以排查的问题⚠️ 不支持不间断空格\u00A0, 全角空格\u3000: 该字符来源极广,且难以和普通空格区分,从而造成混淆
- ✅ 空格
TASON类型实例包括两大类:标量类型(ScalarTypeInstance)和对象类型(ObjectTypeInstance)。
类似GraphQL中的Scalar,代表了一种不会被细分的基元类型,比如各种整数、浮点数、日期时间、正则表达式、UUID等。
- 标量类型使用代表值的字符串进行构造,如
RegExp("/^[A-Z0-9_]+$/i"),Int64("0x1234ABCDEF") - 可以创建自己的标量类型,并且标量类型不受类型的复杂程度限制。例如可以创建一个名为
JSON的标量类型,使用JSON.stringify()和JSON.parse()进行序列化和反序列化一整个字符串 - 默认情况下,标量类型对应的类具有一个构造函数,接受一个代表值的字符串作为参数
对象类型代表一种强类型化的对象,比如各种结构体、类、字典等。
- 对象类型使用对象字面量进行构造,如
Person({ name: "John", 'age': 30 }) - 可以创建自己的对象类型。有两种常见的实现方式:
- 类拥有一个无参构造函数,并在创建后再对对应的属性进行赋值
- 对于动态语言如js来说,为了简化使用,对应的类的构造函数可以接受一个对象字面量作为参数,其中对象的每一个属性都是这个类的公共可写属性名
- 在C#中record类型可以直接注册为对象类型,因为它通常具有类初始化器、复制构造函数等简化对象创建的方法
内置类型的支持正在补充中
- ✅ UInt8 : 8位无符号整数,提供别名Byte
- ✅ Int16 : 16位有符号整数,提供别名Short
- ✅ Int32 : 32位有符号整数,提供别名Int
- ✅ Int64 : 64位有符号整数,提供别名Long
- ✅ Float32 : 32位浮点数,提供别名Single
- ✅ Float64 : 64位浮点数,提供别名Double
- ✅ Decimal128 : 128位有符号十进制数,提供别名Decimal
- ✅ BigInt : 无限精度整数
- Int8 : 8位有符号整数
- UInt16 : 16位无符号整数
- UInt32 : 32位无符号整数
- UInt64 : 64位无符号整数
- Float16 : 16位浮点数,提供别名Half
JavaScript 实现说明:JS/TS 没有定长数值原语(仅有
number/bigint)。本仓库通过 数值处理选项 控制装箱/拆箱,并通过 实体 Schema 把字段收成期望的运行时类型。使用说明:
标准不要求提供UInt8以外的无符号整数类型,以及8位有符号整数。很多语言如Java不支持无符号整数类型,并且被.NET标记为CLS不兼容。
如果你的语言支持这些默认不提供的数字类型,并且可能包含其它范围更小或者更大的数字类型(如Int128),应该提供相应类型的实现;
如果你的语言中对应类型的名称和标准的名称不一致,应当提供别名以简化使用,例如Java中Decimal128的实现采用BigDecimal类型。
在数据传输上,由于无符号类型和对应的有符号类型采用相同的内存表示,因此可以很容易地转换。 其它内存表示为整数或者浮点数的类型(如C#字符类型Char,本机大小的整数IntPtr),除特殊用途外,应该转换为相同内存表示的标准数字类型。
- ✅ Date : 时间日期,使用JavaScript的
Date对象。 时区取决于创建时是否指定了时区标记(例如UTC标记Z或者精确时间偏移+06:30),如果没有标记,默认为本地时区。 - ✅ DateOnly : 仅包含日期,和时区无关。
- ✅ TimeOnly : 仅包含24小时制的时间,和时区无关。
- ✅ Timestamp : UTC时间戳(单位为毫秒),和时区无关。
独立设置时间戳类型是为了解决语义上表达距
1970-01-01T00:00:00Z经过的毫秒数,并且在数据库等场景中往往有单独的处理方式。
Date类型表示时间日期,如果采用DateTime表示时间日期,Date和Time分别表示只有日期和只有时间的类型,
可能会引起混淆。因此这里使用Date表示时间日期,DateOnly和TimeOnly分别表示只有日期和只有时间的类型。
这样在.NET, JavaScript, Java等语言中都不会产生混淆。
- ✅ RegExp: 正则表达式,采用PCRE语法,并用js风格的/.../包裹起来,后面跟上正则表达式选项字符
⚠️ 正则表达式在不同语言的支持存在差异,具体见 正则表达式
- ✅ UUID : UUID/GUID,横杠分隔的形式
- ✅ JSON类型: 包括
JSON,JSONObject和JSONArray。虽然TASON完全支持JSON的语法,但在某些情况下仍然需要使用目标语言所使用的专用JSON类型, 例如Java的JSONObject(fastjson),以便于更好地控制序列化反序列化过程 - ✅ Buffer : 二进制数据,使用base64编码或者HEX字符串
⚠️ 通常Buffer类型用于DTO中用来承载小型二进制数据,例如嵌入的图片、哈希和证书等,以简化序列化过程。 虽然TASON支持序列化字节数组,但不管是序列化还是反序列化,对于二进制数据来说,在体积和性能上开销太大,因此不建议直接使用原生的字节数组类型参与序列化。⚠️ Buffer类型不提供“二进制字符串”,即完全使用转义符+ASCII字符将字节数组序列化成字符串的格式。虽然二进制字符串可以降低序列化开销,但是会带来较多的安全问题,体积上由于转义符的存在也没有优势,因此不提供支持。
- DOM对象:采用HTML/XML字符串表示
- 各种TypedArray, DataView,ArrayBuffer和node.js Buffer:转换为Buffer类型
以下类型可能会导致一些问题,需要谨慎使用
集合类型是指可迭代/可枚举类型,如js的Iterable<T>,c#的IEnumerable<T>等,表达比数组更复杂的集合数据结构。
默认情况下,集合类型将会以数组的形式序列化,因此存在以下限制:
⚠️ 不指定类型反序列化时只能得到数组⚠️ 对象中包含可迭代类型的属性时,如果该对象不能正确处理数组类型到到可迭代对象的转换(通常发生在js和Python等动态语言中),则可能发生异常⚠️ 在C#中,对于泛型可迭代对象,在反序列化时可能因为其中的项违反泛型约束而失败
如果确实需要保留集合类型,可以设置选项useBuiltinCollection为true来序列化为ObjectTypeInstance,而不是数组
⚠️ 启用useBuiltinCollection的情况下,无法保证跨语言的兼容性,例如C#的SortedList<T>在Java中找不到匹配项。
字典类型是指js和java的Map<K, V>,C#的IDictionary<K, V>,Python的dict[K, V]等用于存储键值对的类型;在js中普通对象也常用来当做字典使用。
默认情况下,字典将会以对象的形式序列化,因此存在以下对象类型的限制:
⚠️ 仅支持键为字符串,使用非字符串的键会导致未定义的行为- 在非泛型字典中,如js和Java中通常会丢弃非法的键值对
- 在C#中,如果泛型
Dictionary<K, V>的键类型K不是字符串,则会抛出异常;非泛型字典丢弃字符串以外的键值对
⚠️ 在js中使用数字作为Map的键会在序列化时转换为字符串,反序列化时会产生类型错误⚠️ js 的 Symbol 键 / 值及可迭代协议:见 不安全的类型 · Symbol
如果确实需要非字符串的键(含对象键等),可以设置选项useBuiltinDictionary为true来序列化为内置Dictionary类型,而不是对象
以下类型需要启用
allowUnsafeTypes选项才能使用
⚠️ js的Symbol: 因为其设计上的特殊性,虽然Symbol可以被序列化和反序列化,但不能保证反序列化后的Symbol对象与原始Symbol对象是同一个。- ✅ 可作为属性值 / 数组元素(需
allowUnsafeTypes) - ✅ 可作为 Dictionary/Map 的键或值(需
allowUnsafeTypes;完整往返建议再加useBuiltinDictionary) - 未开启
allowUnsafeTypes时:凡 symbol 进入序列化逻辑(属性值、数组元素、Map/Dictionary 的 key 或 value)一律报错,不得静默丢弃 - 普通对象上的 Symbol 键:
- 未
allowUnsafeTypes:不进入序列化视图(与Object.entries一致),忽略 - 有
allowUnsafeTypes:会尝试读取可枚举 Symbol 键值对- ✅ 有
useBuiltinDictionary→ 提升为Dictionary,Symbol 键可完整序列化 ⚠️ 否则TASON对象语法无法表达 → 忽略 Symbol 键(仅保留字符串键)
- ✅ 有
- 未
- 可迭代协议
[Symbol.iterator]/[Symbol.asyncIterator]:- 必须是方法(
typeof === "function"),否则报错 iterator为方法 → 按 iterable 展开(优先于对象/Dictionary 字段路径)asyncIterator为方法 → 不支持,报错
- 必须是方法(
- ❌ 本地
Symbol()/Symbol("desc")(仅Symbol.for与 well-known) ⚠️ 描述名为 well-known 的Symbol.for("Symbol.toStringTag")反序列化会落到 well-knownSymbol.toStringTag(身份可能变化)
- ✅ 可作为属性值 / 数组元素(需
⚠️ 共享内存对象,如js的SharedArrayBuffer: 由于可以被多线程写入,无法保证值在序列化时是固定的;始终克隆一个ArrayBuffer的副本以防止被修改。⚠️ 指针类型:只能序列化为对应的基础数值类型;无法反序列化,反序列化指针没有意义且具有严重的安全问题
- ❌ 异步包装类,如js的
Promise, c#的Task和ValueTask: 无法在同步方法内调用 - ❌ 异步迭代器和生成器: 无法在同步方法内调用
- ❌ 各种函数和类的定义: 序列化和传输代码是一种高危操作。如果需要在特定条件(例如MongoDB查询)下使用,请使用自定义的类型包装代码
- ❌ 类的静态成员和非公共成员: 按语义不支持。也不会提供选项来控制序列化私有字段,请使用公共属性进行暴露,或者提供自定义序列化方法
- ❌ 循环引用: 序列化循环引用往往会带来各种性能和安全问题,而且需要额外的语法调整,因此不支持。
- ❌ 支持弱引用的类型,如js和Java的
WeakMap,WeakSet: 为了避免影响GC,这些类型都无法枚举,因此无法得到需要序列化的内容