{"record":{"id":"d47f4be5f651e5ae","repo":"angular/angular-cli","slug":"project-property-name-should-be-a-string","errorCode":null,"errorMessage":"Project property \"${name}\" should be a string.","messagePattern":"Project property \"(.+?)\" should be a string\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/angular_devkit/core/src/workspace/json/reader.ts","lineNumber":209,"sourceCode":"  for (const [name, value] of Object.entries<JsonValue>(projectNodeValue)) {\n    switch (name) {\n      case 'targets':\n      case 'architect': {\n        const nodes = findNodeAtLocation(projectNode, [name]);\n        if (!isJsonObject(value) || !nodes) {\n          context.error(`Invalid \"${name}\" field found; expected an object.`, value);\n          break;\n        }\n        hasTargets = true;\n        targets = parseTargetsObject(projectName, nodes, context);\n        jsonMetadata.hasLegacyTargetsName = name === 'architect';\n        break;\n      }\n      case 'prefix':\n      case 'root':\n      case 'sourceRoot':\n        if (typeof value !== 'string') {\n          context.warn(`Project property \"${name}\" should be a string.`, value);\n        }\n        if (properties) {\n          properties[name] = value as string;\n        }\n        break;\n      default:\n        if (!context.unprefixedProjectExtensions.has(name) && !/^[a-z]{1,3}-.*/.test(name)) {\n          context.warn(\n            `Project '${projectName}' contains extension with invalid name (${name}).`,\n            name,\n          );\n        }\n        if (extensions) {\n          extensions[name] = value;\n        }\n        break;\n    }\n  }","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/angular/angular-cli/blob/bb72145f9ab45aee29f523236b3a25cd0813a841/packages/angular_devkit/core/src/workspace/json/reader.ts#L191-L227","documentation":"While parsing a project definition in angular.json, the reader warns when the 'prefix', 'root', or 'sourceRoot' property is present but is not a string. The value is still copied through (cast), but behavior may be broken downstream.","triggerScenarios":"parseProject handles the 'prefix'/'root'/'sourceRoot' cases and finds typeof value !== 'string' (e.g. \"root\": 0 or \"sourceRoot\": true).","commonSituations":"Script-generated angular.json writing numbers/nulls; hand-edits quoting mistakes; template placeholders left unexpanded (e.g. \"root\": null).","solutions":["Set the offending property to a proper string value (e.g. \"root\": \"projects/my-app\").","Remove the property if it has no meaningful value (root defaults).","Fix the generator/script that produced the non-string value."],"exampleFix":"// before (angular.json)\n\"my-app\": { \"root\": 0, \"prefix\": \"app\" }\n// after\n\"my-app\": { \"root\": \"projects/my-app\", \"prefix\": \"app\" }","handlingStrategy":"type-guard","validationCode":"const json = require('./angular.json');\nfor (const [name, p] of Object.entries(json.projects ?? {})) {\n  for (const key of ['prefix', 'root', 'sourceRoot']) {\n    if (key in p && typeof p[key] !== 'string') {\n      throw new Error(`projects.${name}.${key} must be a string, got ${typeof p[key]}: ${JSON.stringify(p[key])}`);\n    }\n  }\n}","typeGuard":"function isStringProp(v) {\n  return typeof v === 'string';\n}","tryCatchPattern":null,"preventionTips":["Keep root/prefix/sourceRoot as strings; quote paths and prefix values.","Ensure generators expand template placeholders before writing angular.json.","Schema-validate angular.json in CI."],"tags":["angular","workspace-configuration","angular-json","validation"],"backgroundTag":"invalid-project-definition","analyzedSha":"bb72145f9ab45aee29f523236b3a25cd0813a841","analyzedAt":"2026-08-30T02:47:34.745Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}