编程 core-types:用 NodeType 统一描述 JSON Schema、TypeScript、GraphQL、OpenAPI 的类型

2026-09-27 00:03:55

core-types:用 NodeType 统一描述 JSON Schema、TypeScript、GraphQL、OpenAPI 的类型

项目信息

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。

使用它的包

版本要求

从 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 变成空数组
  • isPrimitiveType
  • hasConstEnum
  • isEqual:JSON 兼容、非递归类型的 deep-equal
  • intersection
  • union
  • isNonNullable
  • isCoreTypesError
  • decorateErrorMeta
  • decorateError
  • getPositionOffset
  • mergeLocations

Annotations:

  • mergeAnnotations
  • extractAnnotations
  • stringifyAnnotations
  • stripAnnotations
  • stringify

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 两种形式。

推荐文章

程序员茄子在线接单