mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 22:25:05 +00:00
## 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)
109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import fs from 'fs'
|
|
import path from 'path'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import {
|
|
EXPECTED_PROMPT_NAN_COERCED,
|
|
mockFileReaderAbort,
|
|
mockFileReaderError,
|
|
mockFileReaderResult
|
|
} from './__fixtures__/helpers'
|
|
import { getDataFromJSON } from './json'
|
|
|
|
const nanFixturePath = path.resolve(
|
|
__dirname,
|
|
'__fixtures__/with_nan_metadata.json'
|
|
)
|
|
|
|
function jsonFile(content: object): File {
|
|
return new File([JSON.stringify(content)], 'test.json', {
|
|
type: 'application/json'
|
|
})
|
|
}
|
|
|
|
describe('getDataFromJSON', () => {
|
|
it('detects API-format workflows by class_type on every value', async () => {
|
|
const apiData = {
|
|
'1': { class_type: 'KSampler', inputs: {} },
|
|
'2': { class_type: 'EmptyLatentImage', inputs: {} }
|
|
}
|
|
|
|
const result = await getDataFromJSON(jsonFile(apiData))
|
|
|
|
expect(result).toEqual({ prompt: apiData })
|
|
})
|
|
|
|
it('treats objects without universal class_type as a workflow', async () => {
|
|
const workflow = { nodes: [], links: [], version: 1 }
|
|
|
|
const result = await getDataFromJSON(jsonFile(workflow))
|
|
|
|
expect(result).toEqual({ workflow })
|
|
})
|
|
|
|
it('extracts templates when the root object has a templates key', async () => {
|
|
const templates = [{ name: 'basic' }]
|
|
|
|
const result = await getDataFromJSON(jsonFile({ templates }))
|
|
|
|
expect(result).toEqual({ templates })
|
|
})
|
|
|
|
it('parses Python generated API prompt with bare NaN/Infinity tokens', async () => {
|
|
const bytes = fs.readFileSync(nanFixturePath, 'utf-8')
|
|
const file = new File([bytes], 'nan.json', { type: 'application/json' })
|
|
|
|
const result = await getDataFromJSON(file)
|
|
|
|
expect(result).toEqual({ prompt: EXPECTED_PROMPT_NAN_COERCED })
|
|
})
|
|
|
|
it('returns undefined for non-JSON content', async () => {
|
|
const file = new File(['not valid json'], 'bad.json', {
|
|
type: 'application/json'
|
|
})
|
|
|
|
const result = await getDataFromJSON(file)
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
describe('FileReader failure modes', () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('resolves undefined when the FileReader fires error', async () => {
|
|
mockFileReaderError('readAsText')
|
|
|
|
const result = await getDataFromJSON(jsonFile({ nodes: [] }))
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it('resolves undefined when the FileReader fires abort', async () => {
|
|
mockFileReaderAbort('readAsText')
|
|
|
|
const result = await getDataFromJSON(jsonFile({ nodes: [] }))
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it('resolves undefined when reader.result is not a string', async () => {
|
|
mockFileReaderResult('readAsText', new ArrayBuffer(8))
|
|
|
|
const result = await getDataFromJSON(jsonFile({ nodes: [] }))
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it('resolves undefined when reader.result is null', async () => {
|
|
mockFileReaderResult('readAsText', null)
|
|
|
|
const result = await getDataFromJSON(jsonFile({ nodes: [] }))
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
})
|
|
})
|