{"record":{"id":"b0630e947ae3e4d7","repo":"Zie619/n8n-workflows","slug":"invalid-row-structure-b0630e","errorCode":null,"errorMessage":"Invalid row structure","messagePattern":"Invalid row structure","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"workflows/Googleanalytics/1480_Googleanalytics_Code_Automation_Webhook.json","lineNumber":204,"sourceCode":"              \"listName\": \"other\"\n            }\n          ]\n        },\n        \"additionalFields\": {}\n      },\n      \"typeVersion\": 2,\n      \"notes\": \"This googleAnalytics node performs automated tasks as part of the workflow.\"\n    },\n    {\n      \"id\": \"f8dac36b-9e8a-407f-b923-b4cea368f1bc\",\n      \"name\": \"Parse data from Google Analytics\",\n      \"type\": \"n8n-nodes-base.code\",\n      \"position\": [\n        880,\n        460\n      ],\n      \"parameters\": {\n        \"jsCode\": \"function transformToUrlString(items) {\\n // Debug logging\\n console.log('Input items:', JSON.stringify(items, null, 2));\\n \\n // Check if items is an array and has content\\n if (!Array.isArray(items) || items.length === 0) {\\n console.log('Items is not an array or is empty');\\n throw new Error('Invalid data structure');\\n }\\n\\n // Check if first item exists and has json property\\n if (!items[0] || !items[0].json) {\\n console.log('First item is missing or has no json property');\\n throw new Error('Invalid data structure');\\n }\\n\\n // Get the analytics data\\n const analyticsData = items[0].json;\\n \\n // Check if analyticsData has rows\\n if (!analyticsData || !Array.isArray(analyticsData.rows)) {\\n console.log('Analytics data is missing or has no rows array');\\n throw new Error('Invalid data structure');\\n }\\n \\n // Map each row to a simplified object\\n const simplified = analyticsData.rows.map(row => {\\n if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {\\n console.log('Invalid row structure:', row);\\n throw new Error('Invalid row structure');\\n }\\n \\n return {\\n page: row.dimensionValues[0].value,\\n pageViews: parseInt(row.metricValues[0].value) || 0,\\n activeUsers: parseInt(row.metricValues[1].value) || 0,\\n viewsPerUser: parseFloat(row.metricValues[2].value) || 0,\\n eventCount: parseInt(row.metricValues[3].value) || 0\\n };\\n });\\n \\n // Convert to JSON string and encode for URL\\n return encodeURIComponent(JSON.stringify(simplified));\\n}\\n\\n// Get input data and transform it\\nconst urlString = transformToUrlString($input.all());\\n\\n// Return the result\\nreturn { json: { urlString } };\"\n      },\n      \"typeVersion\": 2,\n      \"notes\": \"This code node performs automated tasks as part of the workflow.\"\n    },\n    {\n      \"id\": \"ed880442-c92e-4347-b277-e8794aea6fbc\",\n      \"name\": \"Parse GA data\",\n      \"type\": \"n8n-nodes-base.code\",\n      \"position\": [\n        1240,\n        460\n      ],\n      \"parameters\": {\n        \"jsCode\": \"function transformToUrlString(items) {\\n // Debug logging\\n console.log('Input items:', JSON.stringify(items, null, 2));\\n \\n // Check if items is an array and has content\\n if (!Array.isArray(items) || items.length === 0) {\\n console.log('Items is not an array or is empty');\\n throw new Error('Invalid data structure');\\n }\\n\\n // Check if first item exists and has json property\\n if (!items[0] || !items[0].json) {\\n console.log('First item is missing or has no json property');\\n throw new Error('Invalid data structure');\\n }\\n\\n // Get the analytics data\\n const analyticsData = items[0].json;\\n \\n // Check if analyticsData has rows\\n if (!analyticsData || !Array.isArray(analyticsData.rows)) {\\n console.log('Analytics data is missing or has no rows array');\\n throw new Error('Invalid data structure');\\n }\\n \\n // Map each row to a simplified object\\n const simplified = analyticsData.rows.map(row => {\\n if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {\\n console.log('Invalid row structure:', row);\\n throw new Error('Invalid row structure');\\n }\\n \\n return {\\n page: row.dimensionValues[0].value,\\n pageViews: parseInt(row.metricValues[0].value) || 0,\\n activeUsers: parseInt(row.metricValues[1].value) || 0,\\n viewsPerUser: parseFloat(row.metricValues[2].value) || 0,\\n eventCount: parseInt(row.metricValues[3].value) || 0\\n };\\n });\\n \\n // Convert to JSON string and encode for URL\\n return encodeURIComponent(JSON.stringify(simplified));\\n}\\n\\n// Get input data and transform it\\nconst urlString = transformToUrlString($input.all());\\n\\n// Return the result\\nreturn { json: { urlString } };\"\n      },\n      \"typeVersion\": 2,\n      \"notes\": \"This code node performs automated tasks as part of the workflow.\"\n    },","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/Zie619/n8n-workflows/blob/94007c1445d9258a7da116646b79473e7c7c3282/workflows/Googleanalytics/1480_Googleanalytics_Code_Automation_Webhook.json#L186-L222","documentation":"Literal Error thrown inside the row-mapper of \"Parse data from Google Analytics\" (1480_Googleanalytics_Code_Automation_Webhook.json:204). Unlike errors 64-66 (envelope checks), this fires per-row: a rows array exists but at least one row lacks dimensionValues[0].value or has an empty/absent metricValues array. The upstream GA4 node requests dimension unifiedScreenName and 4 metrics, so a well-formed row must carry exactly that; malformed rows appear when the report includes aggregated placeholder rows or when the response belongs to a different report configuration.","triggerScenarios":"A row where dimensionValues is empty or its first entry has no value (GA4 can return rows for '(not set)' dimensions with unusual shapes), a row with metricValues missing or length 0, or the upstream node's dimension/metric list changed so rows no longer contain unifiedScreenName + 4 metrics.","commonSituations":"Upstream GA4 node edited (dimension renamed, metrics reduced) without updating the parser; response contains an '(other)'/summary row; mixed report types flowing into the parser after a workflow merge.","solutions":["Read the console output — 'Invalid row structure:' prints the offending row verbatim.","Re-open the upstream googleAnalytics node and confirm dimensionsGA4 = [unifiedScreenName] and metricsGA4 still lists the 4 metrics in the original order.","Skip bad rows instead of throwing: change the map to a filter+map so structurally invalid rows are logged and dropped.","If every row is invalid, the upstream report config drifted — fix the GA node, not the parser.","Add an execution-data check: if >50% of rows are skipped, fail loudly with a descriptive Error('GA report shape drifted') instead of the generic message."],"exampleFix":"// before\nconst simplified = analyticsData.rows.map(row => {\n  if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {\n    throw new Error('Invalid row structure');\n  }\n  ...\n});\n// after\nconst simplified = analyticsData.rows\n  .filter(row => {\n    const ok = !!row?.dimensionValues?.[0]?.value && row.metricValues?.length >= 4;\n    if (!ok) console.log('Skipping malformed row:', JSON.stringify(row));\n    return ok;\n  })\n  .map(row => ({\n    page: row.dimensionValues[0].value,\n    pageViews: parseInt(row.metricValues[0].value) || 0,\n    activeUsers: parseInt(row.metricValues[1].value) || 0,\n    viewsPerUser: parseFloat(row.metricValues[2].value) || 0,\n    eventCount: parseInt(row.metricValues[3].value) || 0\n  }));","handlingStrategy":"type-guard","validationCode":"const rows = ($input.all()[0]?.json?.rows ?? []);\nconst badRows = rows.filter(r => !isGaRow(r));\nif (badRows.length) console.log('Malformed rows:', JSON.stringify(badRows.slice(0, 3)));","typeGuard":"const isGaRow = (r) =>\n  !!r?.dimensionValues?.[0]?.value &&\n  Array.isArray(r.metricValues) &&\n  r.metricValues.length >= 4; // 4 metrics configured upstream","tryCatchPattern":"const simplified = rows\n  .filter(isGaRow)\n  .map(r => ({\n    page: r.dimensionValues[0].value,\n    pageViews: parseInt(r.metricValues[0].value) || 0,\n    activeUsers: parseInt(r.metricValues[1].value) || 0,\n    viewsPerUser: parseFloat(r.metricValues[2].value) || 0,\n    eventCount: parseInt(r.metricValues[3].value) || 0\n  }));","preventionTips":["Filter-then-map: one bad row should never abort a whole report.","Keep the parser's expected metric count derived from the GA node config, not a magic number in two places.","Count and log skipped rows to detect report-shape drift early."],"tags":["n8n","code-node","google-analytics","ga4","row-validation","data-parsing"],"backgroundTag":null,"analyzedSha":"94007c1445d9258a7da116646b79473e7c7c3282","analyzedAt":"2026-08-15T04:10:37.591Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}