angular/angular-cli · error · TypeError
"${name}" must be a JSON value.
Error message
"${name}" must be a JSON value. What it means
In ProjectDefinitionCollection.add(), any property that is not one of the known keys (root, sourceRoot, prefix, targets, etc.) is treated as an extension and must be a JSON-serializable value (checked by isJsonValue). Passing functions, undefined, symbols, or other non-JSON values in the definition object throws this TypeError.
Source
Thrown at packages/angular_devkit/core/src/workspace/definitions.ts:182
if (target) {
project.targets.set(name, target);
}
}
}
for (const [name, value] of Object.entries(definition)) {
switch (name) {
case 'name':
case 'root':
case 'sourceRoot':
case 'prefix':
case 'targets':
break;
default:
if (isJsonValue(value)) {
project.extensions[name] = value;
} else {
throw new TypeError(`"${name}" must be a JSON value.`);
}
break;
}
}
super.set(definition.name, project);
return project;
}
override set(name: string, value: ProjectDefinition): this {
this._validateName(name);
super.set(name, value);
return this;
}
View on GitHub (pinned to bb72145f9a)
Solutions
- Only include JSON-serializable values (string, number, boolean, null, plain objects, arrays) for extension properties.
- Move non-serializable behavior (functions, instances) out of the definition object into your own code.
- Validate the definition object before calling add (strip unknown non-JSON keys).
Example fix
// before
workspace.addProject({ name: 'app', root: 'apps/app', hooks: { init: fn } });
// after
workspace.addProject({ name: 'app', root: 'apps/app', hooksDescription: 'init handled externally' }); Defensive patterns
Strategy: type-guard
Validate before calling
function isJsonValue(v: unknown): boolean {
return v === null || ['string', 'number', 'boolean'].includes(typeof v)
|| (Array.isArray(v) && v.every(isJsonValue))
|| (typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every(isJsonValue));
}
// strip non-JSON extra keys before add
const extras = Object.fromEntries(Object.entries(def).filter(([, v]) => isJsonValue(v))); Type guard
function isJsonValue(v: unknown): boolean {
if (v === null || ['string', 'number', 'boolean'].includes(typeof v)) return true;
if (Array.isArray(v)) return v.every(isJsonValue);
if (typeof v === 'object' && v !== null) return Object.values(v).every(isJsonValue);
return false;
} Try / catch
try {
project = workspace.addProject(def);
} catch (e) {
if (e instanceof TypeError && /must be a JSON value/.test(e.message)) {
const { [e.message.match(/"(.+)"/)?.[1] ?? '']: _dropped, ...rest } = def;
project = workspace.addProject(rest);
} else throw e;
} Prevention
- Never pass functions, class instances, undefined, or symbols in project definition objects
- JSON.stringify-test extension values before adding them
- Keep extension data limited to plain objects, arrays, strings, numbers, booleans, and null
When it happens
Trigger: Calling addProject/add with an extra property whose value is a function, class instance, undefined, symbol, or otherwise not JSON-representable, e.g. { name: 'app', root: '', onInit: () => {} }.
Common situations: Passing callbacks/objects into project definitions expecting them to be stored as extensions; leaking class instances from tooling into angular.json extensions; typos creating unexpected keys with non-JSON values.
Related errors
- Target name must be a string.
- Project name already exists.
- Project name must be a valid npm package name.
- Target name already exists.
- Unable to read workspace file.
AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30).
Data as JSON: /api/errors/2643c0a0f8021e8c.
Report an issue: GitHub.