Yiwei Mao 867ceb8bc2
fix(materials): json schema title & variable selector long text style (#438)
* fix: json schema title

* fix: variable selector style

* feat: add missing license header

* feat: add doc build in gitignore
2025-07-01 12:44:12 +00:00

181 lines
4.8 KiB
TypeScript

/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import { get } from 'lodash';
import { ASTFactory, ASTKind, ASTMatch, ASTNode, ASTNodeJSON, BaseType } from '@flowgram.ai/editor';
import { IJsonSchema } from '../../typings/json-schema';
export namespace JsonSchemaUtils {
/**
* Converts a JSON schema to an Abstract Syntax Tree (AST) representation.
* This function recursively processes the JSON schema and creates corresponding AST nodes.
*
* For more information on JSON Schema, refer to the official documentation:
* https://json-schema.org/
*
* @param jsonSchema - The JSON schema to convert.
* @returns An AST node representing the JSON schema, or undefined if the schema type is not recognized.
*/
export function schemaToAST(jsonSchema: IJsonSchema): ASTNodeJSON | undefined {
const { type, extra } = jsonSchema || {};
const { weak = false } = extra || {};
if (!type) {
return undefined;
}
switch (type) {
case 'object':
if (weak) {
return { kind: ASTKind.Object, weak: true };
}
return ASTFactory.createObject({
properties: Object.entries(jsonSchema.properties || {})
/**
* Sorts the properties of a JSON schema based on the 'extra.index' field.
* If the 'extra.index' field is not present, the property will be treated as having an index of 0.
*/
.sort((a, b) => (get(a?.[1], 'extra.index') || 0) - (get(b?.[1], 'extra.index') || 0))
.map(([key, _property]) => ({
key,
type: schemaToAST(_property),
meta: {
title: _property.title,
description: _property.description,
},
})),
});
case 'array':
if (weak) {
return { kind: ASTKind.Array, weak: true };
}
return ASTFactory.createArray({
items: schemaToAST(jsonSchema.items!),
});
case 'map':
if (weak) {
return { kind: ASTKind.Map, weak: true };
}
return ASTFactory.createMap({
valueType: schemaToAST(jsonSchema.additionalProperties!),
});
case 'string':
return ASTFactory.createString();
case 'number':
return ASTFactory.createNumber();
case 'boolean':
return ASTFactory.createBoolean();
case 'integer':
return ASTFactory.createInteger();
default:
// If the type is not recognized, return CustomType
return ASTFactory.createCustomType({ typeName: type });
}
}
/**
* Convert AST To JSON Schema
* @param typeAST
* @returns
*/
export function astToSchema(
typeAST: ASTNode,
options?: { drilldown?: boolean }
): IJsonSchema | undefined {
const { drilldown = true } = options || {};
if (ASTMatch.isString(typeAST)) {
return {
type: 'string',
};
}
if (ASTMatch.isBoolean(typeAST)) {
return {
type: 'boolean',
};
}
if (ASTMatch.isNumber(typeAST)) {
return {
type: 'number',
};
}
if (ASTMatch.isInteger(typeAST)) {
return {
type: 'integer',
};
}
if (ASTMatch.isObject(typeAST)) {
return {
type: 'object',
properties: drilldown
? Object.fromEntries(
typeAST.properties.map((property) => {
const schema = astToSchema(property.type);
if (property.meta?.title && schema) {
schema.title = property.meta.title;
}
if (property.meta?.description && schema) {
schema.description = property.meta.description;
}
return [property.key, schema!];
})
)
: {},
};
}
if (ASTMatch.isArray(typeAST)) {
return {
type: 'array',
items: drilldown ? astToSchema(typeAST.items) : undefined,
};
}
if (ASTMatch.isMap(typeAST)) {
return {
type: 'map',
items: drilldown ? astToSchema(typeAST.valueType) : undefined,
};
}
if (ASTMatch.isCustomType(typeAST)) {
return {
type: typeAST.typeName,
};
}
return undefined;
}
/**
* Check if the AST type is match the JSON Schema
* @param typeAST
* @param schema
* @returns
*/
export function isASTMatchSchema(
typeAST: BaseType,
schema: IJsonSchema | IJsonSchema[]
): boolean {
if (Array.isArray(schema)) {
return typeAST.isTypeEqual(
ASTFactory.createUnion({
types: schema.map((_schema) => schemaToAST(_schema)!).filter(Boolean),
})
);
}
return typeAST.isTypeEqual(schemaToAST(schema));
}
}