export type NormalizedTreeNode> = { id: string; data: TData; children: Array>; }; type TreeNormalizationOptions> = { isTreeData: (value: unknown) => value is TData; createEmptyData: () => TData; }; type RawTreeNodeKey = "id" | "meta" | "children"; export function normalizeTreeJson>( input: unknown, options: TreeNormalizationOptions, ): Array> { if (!Array.isArray(input)) return []; return input.map((node) => normalizeTreeNode(node, options)); } function normalizeTreeNode>( node: unknown, options: TreeNormalizationOptions, ): NormalizedTreeNode { const rawId = getRawTreeNodeField(node, "id"); const rawMeta = getRawTreeNodeField(node, "meta"); const rawChildren = getRawTreeNodeField(node, "children"); return { id: typeof rawId === "string" ? rawId : "", data: options.isTreeData(rawMeta) ? rawMeta : options.createEmptyData(), children: Array.isArray(rawChildren) ? rawChildren.map((child) => normalizeTreeNode(child, options)) : [], }; } function getRawTreeNodeField(node: unknown, key: RawTreeNodeKey): unknown { if (!isPlainRecord(node)) return undefined; return node[key]; } function isPlainRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); }