core-types:用 NodeType 统一描述 JSON Schema、TypeScript、GraphQL、OpenAPI 的类型
项目信息
- GitHub: grantila/core-types
- npm: core-types
- 子包:
- core-types-json-schema
- core-types-ts
- core-types-graphql
- core-types-suretype
- typeconv
core-types 是一个 npm 包,提供 TypeScript 类型描述,作为 JSON Schema / TypeScript / GraphQL / OpenAPI 之间的中间类型模型(lowest common denominator)。它包含用于简化不必要复杂类型的函数,也提供 helper,供其他包在 core-types 和其他类型系统之间转换时使用。
由于 core-types 相对小、定义清晰,用它实现到其他类型系统的转换比较直接。
用途:简化类型、merge、flatten、移除不必要类型;验证类型;遍历类型树;some(类型树上的 Array.prototype.some);为包实现者提供 helpers。
类型规范包括:any、null、boolean(true/false)、string、number 和 integer(JSON Schema 中区分,TypeScript 中等价)、object(key-value,key 是 string)、array(任意长度、特定 core type 的列表)、tuple(特定长度、每个位置有区分 core type 的列表)、ref(对命名类型的引用)、union {or:[...]}、intersection {and:[...]}。
上述类型可以完整描述 JSON,也是描述 JSON、JSON Schema 和 TypeScript 类型时的最低公分母。可以把它看成极度简化版的 JSON Schema。
使用它的包
- core-types-json-schema:转换到/从 JSON Schema / Open API
- core-types-ts:转换到/从 TypeScript 类型/interface
- core-types-graphql:转换到/从 GraphQL
- core-types-suretype:转换到/从 SureType validator schema
- typeconv:通过 core-types 在 JSON Schema、TypeScript、GraphQL 和 Open API 之间转换
版本要求
从 3.0 起,这个包是纯 ESM,要求 Node 14.13.1 或更高版本。
Usage
创建一个 core-types 类型,只需要把它 cast 成 NodeType。
import type { NodeType } from 'core-types'
const myStringType: NodeType = { type: 'string' };
simplify
接收一个类型或类型数组,返回简化后的类型定义。例如:空的 and/or 被移除;any 和 string 的 union,除 const/enum 用法外,简化为 any;any 和 string 的 intersection 变为 string;包含子 or 的 or 展平到父 or。类型上无损,但可能移除 annotations(descriptions)。通常建议在类型转换到 core-types 后、转换到另一个类型系统前调用 simplify。
import { simplify } from 'core-types'
const simplified = simplify( myType );
validate
验证一个 NodeType 类型树是否有效。例如确保 minItems 是非负整数;如果同时指定了 enum 和 const,确保它们不冲突。
import { validate } from 'core-types'
validate( myType ); // Throws error if not valid
traverse
遍历类型树,对每个节点调用回调。回调参数:{ node, rootNode, path, parentProperty?, parentNode?, index?, required? }。
import { traverse } from 'core-types'
traverse( rootNode, ( { node } ) => { if ( !node.title ) node.title = "This is a dummy title"; } );
some
类似 traverse,但回调返回 boolean;任一节点满足条件则返回 true;类似 Array.prototype.some。
import { some } from 'core-types'
const hasRefNode = some( rootNode, ( { node: { type } } ) => type === 'ref' );
helpers
ensureArray:null/undefined 变成空数组isPrimitiveTypehasConstEnumisEqual:JSON 兼容、非递归类型的 deep-equalintersectionunionisNonNullableisCoreTypesErrordecorateErrorMetadecorateErrorgetPositionOffsetmergeLocations
Annotations:
mergeAnnotationsextractAnnotationsstringifyAnnotationsstripAnnotationsstringify
Conversion:转换包推荐返回 ConversionResult { data: T; convertedTypes: string[]; notConvertedTypes: string[] }。
Specification
主类型 NodeType 是具体类型的 union,始终有一个 type 属性,取值为 Types:
type Types = 'any' | 'null' | 'boolean' | 'string' | 'number' | 'integer' | 'object' | 'array' | 'tuple' | 'ref' | 'and' | 'or';
type NodeType = AnyType | NullType | BooleanType | StringType | NumberType | IntegerType | ObjectType | ArrayType | TupleType | RefType | AndType | OrType;
类型有可选的 name(string),可以通过 NamedType 转成必填。转换包应使用 NodeDocument:
interface NodeDocument { version: 1; types: Array; }
可选 annotations:title、description、examples、default、see、comment。
除了 NullType、AndType、OrType,所有类型都可以有 const(T)或 enum(Array),语义同 JSON Schema;const 等于只有一个值的 enum;enum 近似 TS 的 type literal union。
any type
匹配任何类型;const/enum 的元素类型未知;对应 TS 的 any/unknown 和 JSON Schema 的空 schema {}。示例:{ type: 'any' }。
null type
等价于 null。{ type: 'null' }
boolean type
BooleanType;const/enum 的 T 是 boolean。{ type: 'boolean', const: false }
string type
StringType;T 是 string。{ type: 'string', enum: ["foo","bar"] }
number type
区分 NumberType 和 IntegerType。在 TS/JS/JSON 中两者都等于 Number;在 JSON Schema 中 integer 是独立类型,可以转换并保留类型信息。T 是 number。{ type: 'number', enum: [17,42] }
object type
ObjectType 描述 Record / JSON Object;key 必须是字符串。const/enum 的 T 是 Record。另外有两个必填属性:properties 和 additionalProperties。
properties:Record。
additionalProperties:boolean | NodeType。false 表示不允许额外属性;true 表示额外属性可以是任意类型;否则额外属性必须匹配指定的 NodeType。
示例:properties 中包含 name、age、level,additionalProperties 为 false。
array type
ArrayType 描述 Array;必填属性 elementType: NodeType。
{ type: 'array', elementType: { type: 'string' } }
tuple type
TupleType 描述特定长度的数组,每个位置有各自的类型;匹配 TS 的 tuple [A,B,...]。必填:
elementTypes: [...NodeType]minItems:整数,不能为负;如果大于elementTypes长度,某些转换会限制到该长度additionalItems:boolean | NodeType
ref type
RefType 引用其他命名类型;推荐一个 NodeTypes 列表中的 ref 指向列表内的命名类型;对应 TS 同文件中的命名类型,或 JSON Schema 中对 #/definitions/* 的 $ref。必填属性 ref(字符串名称)。示例:User/UserList。
union type
OrType;等价于 TS 的 union (number|string) 和 JSON Schema 的 anyOf。必填属性 or: Array。示例:or: [string, number, ref IdType]。
intersection type
AndType;等价于 TS 的 intersection (A&B) 和 JSON Schema 的 allOf。必填属性 and: Array。示例:CommentWithId = and:[ref Comment, ref WithId]。
取舍与坑点
number 和 integer 在 core-types 里分开建模,但 TS 的 number 同时覆盖整数和浮点,JSON 也只有 number。转换到 TS 时,integer 会变成 number;再从 TS 转回 JSON Schema 时,如果没有额外 annotation,integer 语义可能丢失。JSON Schema 中 integer 是独立类型,能保留时应尽量保留。
simplify 在类型上无损,但可能移除 annotations,比如 description。如果后续步骤依赖 title、description、examples、default、see、comment,simplify 应放在最后,或先提取 annotations。
ref 只推荐指向同一 NodeDocument 内的命名类型。它对应 TS 同文件命名类型、JSON Schema $ref 到 #/definitions/* 的情况;跨文档引用不要依赖 ref。
additionalProperties 有三种写法:false 不允许额外属性;true 允许任意类型的额外属性;传 NodeType 则额外属性必须匹配该 NodeType。对应 JSON Schema 里 boolean/schema 两种形式。