Zie619/n8n-workflows · error · Error

Invalid row structure

Error message

Invalid row structure

What it means

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.

Source

Thrown at workflows/Googleanalytics/1480_Googleanalytics_Code_Automation_Webhook.json:204

              "listName": "other"
            }
          ]
        },
        "additionalFields": {}
      },
      "typeVersion": 2,
      "notes": "This googleAnalytics node performs automated tasks as part of the workflow."
    },
    {
      "id": "f8dac36b-9e8a-407f-b923-b4cea368f1bc",
      "name": "Parse data from Google Analytics",
      "type": "n8n-nodes-base.code",
      "position": [
        880,
        460
      ],
      "parameters": {
        "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 } };"
      },
      "typeVersion": 2,
      "notes": "This code node performs automated tasks as part of the workflow."
    },
    {
      "id": "ed880442-c92e-4347-b277-e8794aea6fbc",
      "name": "Parse GA data",
      "type": "n8n-nodes-base.code",
      "position": [
        1240,
        460
      ],
      "parameters": {
        "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 } };"
      },
      "typeVersion": 2,
      "notes": "This code node performs automated tasks as part of the workflow."
    },

View on GitHub (pinned to 94007c1445)

Solutions

  1. Read the console output — 'Invalid row structure:' prints the offending row verbatim.
  2. Re-open the upstream googleAnalytics node and confirm dimensionsGA4 = [unifiedScreenName] and metricsGA4 still lists the 4 metrics in the original order.
  3. Skip bad rows instead of throwing: change the map to a filter+map so structurally invalid rows are logged and dropped.
  4. If every row is invalid, the upstream report config drifted — fix the GA node, not the parser.
  5. 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.

Example fix

// before
const simplified = analyticsData.rows.map(row => {
  if (!row.dimensionValues?.[0]?.value || !row.metricValues?.length) {
    throw new Error('Invalid row structure');
  }
  ...
});
// after
const simplified = analyticsData.rows
  .filter(row => {
    const ok = !!row?.dimensionValues?.[0]?.value && row.metricValues?.length >= 4;
    if (!ok) console.log('Skipping malformed row:', JSON.stringify(row));
    return ok;
  })
  .map(row => ({
    page: row.dimensionValues[0].value,
    pageViews: parseInt(row.metricValues[0].value) || 0,
    activeUsers: parseInt(row.metricValues[1].value) || 0,
    viewsPerUser: parseFloat(row.metricValues[2].value) || 0,
    eventCount: parseInt(row.metricValues[3].value) || 0
  }));
Defensive patterns

Strategy: type-guard

Validate before calling

const rows = ($input.all()[0]?.json?.rows ?? []);
const badRows = rows.filter(r => !isGaRow(r));
if (badRows.length) console.log('Malformed rows:', JSON.stringify(badRows.slice(0, 3)));

Type guard

const isGaRow = (r) =>
  !!r?.dimensionValues?.[0]?.value &&
  Array.isArray(r.metricValues) &&
  r.metricValues.length >= 4; // 4 metrics configured upstream

Try / catch

const simplified = rows
  .filter(isGaRow)
  .map(r => ({
    page: r.dimensionValues[0].value,
    pageViews: parseInt(r.metricValues[0].value) || 0,
    activeUsers: parseInt(r.metricValues[1].value) || 0,
    viewsPerUser: parseFloat(r.metricValues[2].value) || 0,
    eventCount: parseInt(r.metricValues[3].value) || 0
  }));

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Zie619/n8n-workflows@94007c1445 (2026-08-15). Data as JSON: /api/errors/b0630e947ae3e4d7. Report an issue: GitHub.