chore(core): Allow undefined in the node output value (#18198)

This commit is contained in:
Guillaume Jacquart
2025-08-11 15:25:52 +02:00
committed by GitHub
parent 409085e8fe
commit 660f2ff09b
3 changed files with 40 additions and 52 deletions

View File

@@ -99,12 +99,6 @@ describe('isJsonCompatible', () => {
errorPath: 'value.nan',
errorMessage: 'is NaN, which is not JSON-compatible',
},
{
name: 'undefined',
value: { undefined },
errorPath: 'value.undefined',
errorMessage: 'is of unknown type (undefined) with value undefined',
},
{
name: 'an object with symbol keys',
value: { [Symbol.for('key')]: 1 },
@@ -125,6 +119,7 @@ describe('isJsonCompatible', () => {
const objectRef = {};
test.each([
{ name: 'null', value: { null: null } },
{ name: 'undefined', value: { noValue: undefined } },
{ name: 'an array of primitives', value: { array: [1, 'string', true, false] } },
{
name: 'an object without a prototype chain',

View File

@@ -1,3 +1,4 @@
// eslint-disable-next-line complexity
const check = (
val: unknown,
path = 'value',
@@ -6,7 +7,7 @@ const check = (
): { isValid: true } | { isValid: false; errorPath: string; errorMessage: string } => {
const type = typeof val;
if (val === null || type === 'boolean' || type === 'string') {
if (val === null || type === 'boolean' || type === 'string' || type === 'undefined') {
return { isValid: true };
}
@@ -46,52 +47,44 @@ const check = (
return { isValid: true };
}
if (type === 'object') {
if (stack.has(val)) {
return {
isValid: false,
errorPath: path,
errorMessage: 'contains a circular reference',
};
}
stack.add(val);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const proto = Object.getPrototypeOf(val);
if (proto !== Object.prototype && proto !== null) {
return {
isValid: false,
errorPath: path,
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
errorMessage: `has non-plain prototype (${proto?.constructor?.name || 'unknown'})`,
};
}
for (const key of Reflect.ownKeys(val as object)) {
if (typeof key === 'symbol') {
return {
isValid: false,
errorPath: `${path}.${key.toString()}`,
errorMessage: `has a symbol key (${String(key)}), which is not JSON-compatible`,
};
}
if (keysToIgnore.has(key)) {
continue;
}
const subVal = (val as Record<string, unknown>)[key];
const result = check(subVal, `${path}.${key}`, stack, keysToIgnore);
if (!result.isValid) return result;
}
stack.delete(val);
return { isValid: true };
if (stack.has(val)) {
return {
isValid: false,
errorPath: path,
errorMessage: 'contains a circular reference',
};
}
stack.add(val);
return {
isValid: false,
errorPath: path,
errorMessage: `is of unknown type (${type}) with value ${JSON.stringify(val)}`,
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const proto = Object.getPrototypeOf(val);
if (proto !== Object.prototype && proto !== null) {
return {
isValid: false,
errorPath: path,
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
errorMessage: `has non-plain prototype (${proto?.constructor?.name || 'unknown'})`,
};
}
for (const key of Reflect.ownKeys(val as object)) {
if (typeof key === 'symbol') {
return {
isValid: false,
errorPath: `${path}.${key.toString()}`,
errorMessage: `has a symbol key (${String(key)}), which is not JSON-compatible`,
};
}
if (keysToIgnore.has(key)) {
continue;
}
const subVal = (val as Record<string, unknown>)[key];
const result = check(subVal, `${path}.${key}`, stack, keysToIgnore);
if (!result.isValid) return result;
}
stack.delete(val);
return { isValid: true };
};
/**