Files
ComfyUI_frontend/src/platform/workflow/utils/workflowExtractionUtil.ts
pythongosssss de1c1ee1f2 fix: add support for parsing python generated json with NaN/infinite (#12217)
## Summary

API and other legacy JSON generated by python `json.dumps` can contain
`NaN` and `Infinity` which cannot be parsed with JS `JSON.parse`. This
adds regex to replace these invalid tokens with `null`.

## Changes

- **What**: 
- add regex replace on bare NaN/infinity tokens after JSON.parse fails
- update call sites
- tests

## Review Focus
- The regex should only rewrite bare NaN/-Infinity/Infinity and not
touch string values or other invalid tokens.
- A small regex was chosen over JSON5 due to package size (30.3kB
Minified, 9kB Minified + Gzipped) or a manual parser due to the
unnecessarily complexity vs a single regex replace.
- The happy path is run first, the safe parse is only executed if that
failed, meaning no overhead the vast majority of the time and no
possiblity of corrupting valid workflows due to a bug in the fallback
parser
- Multiple call sites had to be updated due to pre-existing architecture
of the various parsers, an issue for unifying these is logged for future
cleanup
- New binary fixtures added for validating e2e import using real files

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12217-fix-add-support-for-parsing-python-generated-json-with-NaN-infinite-35f6d73d365081889fc7f4af823f29c1)
by [Unito](https://www.unito.io)
2026-05-13 20:33:19 +00:00

93 lines
3.0 KiB
TypeScript

/**
* Utilities for extracting workflows from different sources
* Supports both job-based and asset-based workflow extraction
*/
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
import { getAssetUrl } from '@/platform/assets/utils/assetUrlUtil'
import { getWorkflowDataFromFile } from '@/scripts/metadata/parser'
import { getJobWorkflow } from '@/services/jobOutputCache'
import { parseJsonWithNonFinite } from '@/utils/jsonUtil'
/**
* Extract workflow from AssetItem using jobs API
* For output assets: uses jobs API (getJobWorkflow)
* For input assets: extracts from file metadata
*
* @param asset The asset item to extract workflow from
* @returns WorkflowSource with workflow and generated filename
*
* @example
* const asset = { name: 'output.png', user_metadata: { jobId: '123' } }
* const { workflow, filename } = await extractWorkflowFromAsset(asset)
*/
export async function extractWorkflowFromAsset(asset: AssetItem): Promise<{
workflow: ComfyWorkflowJSON | null
filename: string
}> {
const baseFilename = asset.name.replace(/\.[^/.]+$/, '.json')
// For output assets: use jobs API (with caching and validation)
const metadata = getOutputAssetMetadata(asset.user_metadata)
if (metadata?.jobId) {
const workflow = await getJobWorkflow(metadata.jobId)
return { workflow: workflow ?? null, filename: baseFilename }
}
// For input assets: extract from file metadata (PNG/WEBP/FLAC with embedded workflow)
try {
const fileUrl = getAssetUrl(asset)
const response = await fetch(fileUrl)
if (!response.ok) {
return { workflow: null, filename: baseFilename }
}
const blob = await response.blob()
const file = new File([blob], asset.name, { type: blob.type })
const workflowData = await getWorkflowDataFromFile(file)
if (workflowData?.workflow) {
// Handle both string and object workflow data
const workflow =
typeof workflowData.workflow === 'string'
? parseJsonWithNonFinite<ComfyWorkflowJSON>(workflowData.workflow)
: (workflowData.workflow as ComfyWorkflowJSON)
return {
workflow,
filename: baseFilename
}
}
} catch (error) {
console.error('Failed to extract workflow from asset:', error)
}
return {
workflow: null,
filename: baseFilename
}
}
/**
* Check if a file format supports embedded workflow metadata
* Useful for UI to show/hide workflow-related options
*
* @param filename The filename to check
* @returns true if the format can contain workflow metadata
*
* @example
* supportsWorkflowMetadata('image.png') // true
* supportsWorkflowMetadata('image.jpg') // false
*/
export function supportsWorkflowMetadata(filename: string): boolean {
const lower = filename.toLowerCase()
return (
lower.endsWith('.png') ||
lower.endsWith('.webp') ||
lower.endsWith('.flac') ||
lower.endsWith('.json')
)
}