You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Apps Script批量数组遇TypeError时插入空单元格而非终止导入?

修复Google Sheets批量JSON解析中缺失字段导致的代码终止问题

我在Google Sheets中处理大规模JSON链接数据集,需要将其转换为结构化数据。当前使用的批量数组代码能正常处理大部分数据,但存在一个问题:当某条数据不存在abbreviation字段时,会触发错误TypeError: Cannot read properties of undefined (reading 'abbreviation'),导致整个代码终止执行。我希望代码遇到此类缺失值时,插入空单元格并继续处理后续数据(就像代码自动处理jersey、debutYear等变量的方式一样)。

我尝试过精简代码、更换Position字段的取值、参考同类可行代码,都没解决问题。之后按建议用了Try/Catch机制,但处理完所有批次后还是出现相同错误,没达到预期效果。


原代码

var scriptProperties = PropertiesService.getScriptProperties();

function dataImport() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Base JSON Import");
  var exportSheet = ss.getSheetByName("Base Data");
  var reqs = sheet.getRange("A2:A" + sheet.getLastRow()).getDisplayValues().reduce((ar, [url]) => {
    if (url) {
      ar.push({ url, muteHttpExceptions: true });
    }
    return ar;
  }, []);

  //Storage of current data
  var bucket = [];
  var batchSize = 200;
  var batches = batchArray(reqs, batchSize);
  var startingBatch = scriptProperties.getProperty("batchNumber") == null ? 0 : parseInt(scriptProperties.getProperty("batchNumber"));
  var processedBatches = scriptProperties.getProperty("processedBatches") == null ? 0 : parseInt(scriptProperties.getProperty("processedBatches"));

  console.log(`Total: ${reqs.length}.\n${batches.length} batches.`)

  if (processedBatches >= (batches.length - 1)) {
    console.log('All data has been processed already.');
  } else {
    //Start from the very last batch that stopped that needs to be processed.
    for (let i = startingBatch; i < batches.length; i++) {
      console.log(`Processing batch index #${parseInt(i)}`);
      try {
        var responses = UrlFetchApp.fetchAll(batches[i]);
        bucket.push(responses);
        //Remove previous batch index number
        scriptProperties.deleteProperty("processedBatches");
        //Store latest sucessful batch index number
        scriptProperties.setProperty("processedBatches", parseInt(i));
      }
      //Catch the last batch index number where it stopped due to URL fetch exception
      catch (e) {
        //Remove the old batch number to be replaced with new batch number.
        scriptProperties.deleteProperty("batchNumber");
        //Remember the last batch that encountered and error to be processed again in the next call.
        scriptProperties.setProperty("batchNumber", parseInt(i));
        console.log(`Batch index #${parseInt(i)} stopped`);
        break;
      }
    }

    const initialRes = [].concat.apply([], bucket);

    var temp = initialRes.reduce((ar, r) => {
      if (r.getResponseCode() == 200) {
        var { id, firstName, lastName, fullName, displayName, shortName, weight, height, position: { abbreviation }, dateOfBirth, hand: { displayValue }, jersey, debutYear, birthPlace: { city }, birthPlace: { state, country }, experience: { years }, active } = JSON.parse(r.getContentText());
        ar.push([id, firstName, lastName, fullName, displayName, shortName, weight, height, abbreviation, dateOfBirth, displayValue, jersey, debutYear, city, state, country, years, active]);
      }
      return ar;
    }, []);
    var res = [...temp];

    //Add table headers
    exportSheet.getLastRow() == 0 && exportSheet.appendRow(['IDs', 'First Name', 'Last Name', 'Full Name', 'Display Name', 'Short Name', 'Weight', 'Height', 'Position', 'DOB', 'Hand', 'Jersey', 'Debut Year', 'City', 'State', 'Country', 'Years', 'Active']);

    //Add table data
    var result = () => {
      return temp.length != 0 && exportSheet.getRange(exportSheet.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
    }
    result() && console.log(`Processed: ${res.length}.`);
  }
}

//Function to chunk the request data based on batch sizes
function batchArray(arr, batchSize) {
  var batches = [];

  for (var i = 0; i < arr.length; i += batchSize) {
    batches.push(arr.slice(i, i + batchSize));
  }

  return batches;
}

尝试修改后的代码

function getBaseJson() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Base JSON Import");
  var countRow = 1;
  var page = 1;
  var url = "https://sports.core.api.espn.com/v2/sports/hockey/leagues/nhl/athletes?limit=1000&page=";
  var response = UrlFetchApp.fetch(url + page);
  var data = response.getContentText();
  var result = JSON.parse(data);
  var { items, pageCount } = result;
  items = items.map(e => [e["$ref"]])
  var reqs = []
  for (var p = 2; p <= pageCount; p++) {
    reqs.push(url + p)
  }
  var responses = UrlFetchApp.fetchAll(reqs);
  var temp = responses.flatMap(r => r.getResponseCode() == 200 ? JSON.parse(r.getContentText()).items.map(e => [e["$ref"]]) : []);
  var res = [['JSONs'], ...items, ...temp];
  sheet.getRange(countRow, 1, res.length).setValues(res);
}

var scriptProperties = PropertiesService.getScriptProperties();

function dataImport() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Base JSON Import");
  var exportSheet = ss.getSheetByName("Base Data");
  var reqs = sheet.getRange("A2:A" + sheet.getLastRow()).getDisplayValues().reduce((ar, [url]) => {
    if (url) {
      ar.push({ url, muteHttpExceptions: true });
    }
    return ar;
  }, []);

  //Storage of current data
  var bucket = [];
  var batchSize = 200;
  var batches = batchArray(reqs, batchSize);
  var startingBatch = scriptProperties.getProperty("batchNumber") == null ? 0 : parseInt(scriptProperties.getProperty("batchNumber"));
  var processedBatches = scriptProperties.getProperty("processedBatches") == null ? 0 : parseInt(scriptProperties.getProperty("processedBatches"));

  console.log(`Total: ${reqs.length}.\n${batches.length} batches.`)

  if (processedBatches >= (batches.length - 1)) {
    console.log('All data has been processed already.');
  } else {
    //Start from the very last batch that stopped that needs to be processed.
    for (let i = startingBatch; i < batches.length; i++) {
      console.log(`Processing batch index #${parseInt(i)}`);
      try {
        var responses = UrlFetchApp.fetchAll(batches[i]);
        bucket.push(responses);
        //Remove previous batch index number
        scriptProperties.deleteProperty("processedBatches");
        //Store latest sucessful batch index number
        scriptProperties.setProperty("processedBatches", parseInt(i));
      }
      //Catch the last batch index number where it stopped due to URL fetch exception
      catch (e) {
        //Remove the old batch number to be replaced with new batch number.
        scriptProperties.deleteProperty("batchNumber");
        //Remember the last batch that encountered and error to be processed again in the next call.
        scriptProperties.setProperty("batchNumber", parseInt(i));
        console.log(`Batch index #${parseInt(i)} stopped`);
        break;
      }
    }

    const initialRes = [].concat.apply([], bucket);

    var temp = initialRes.reduce((ar, r) => {
      if (r.getResponseCode() == 200) {
        var { id, firstName, lastName, fullName, displayName, shortName, weight, height, dateOfBirth, hand: { displayValue }, jersey, debutYear, birthPlace: { city }, birthPlace: { state, country }, experience: { years }, active } = JSON.parse(r.getContentText());
        try {
          var { position: { abbreviation } } = JSON.parse(r.getContentText());
        }
        catch(err) {
          message.innerHTML = "Error: " + err + ".";
        }
        finally {
          var { position: { abbreviation } } = "None"
        }
        ar.push([id, firstName, lastName, fullName, displayName, shortName, weight, height, abbreviation, dateOfBirth, displayValue, jersey, debutYear, city, state, country, years, active]);
      }
      return ar;
    }, []);
    var res = [...temp];

    //Add table headers
    exportSheet.getLastRow() == 0 && exportSheet.appendRow(['IDs', 'First Name', 'Last Name', 'Full Name', 'Display Name', 'Short Name', 'Weight', 'Height', 'Position', 'DOB', 'Hand', 'Jersey', 'Debut Year', 'City', 'State', 'Country', 'Years', 'Active']);

    //Add table data
    var result = () => {
      return temp.length != 0 && exportSheet.getRange(exportSheet.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
    }
    result() && console.log(`Processed: ${res.length}.`);
  }
}

//Function to chunk the request data based on batch sizes
function batchArray(arr, batchSize) {
  var batches = [];

  for (var i = 0; i < arr.length; i += batchSize) {
    batches.push(arr.slice(i, i + batchSize));
  }

  return batches;
}

解决方案

问题出在嵌套字段的解构逻辑上:直接解构position: { abbreviation }会在position不存在时抛出错误,且你尝试的Try/Catch用法存在无效操作(比如message.innerHTML在Google Apps Script环境中不存在,finally块的赋值逻辑错误)。正确的做法是用可选链操作符结合默认值安全获取嵌套字段,无需Try/Catch即可处理缺失情况。

核心修改部分

替换原代码中解析JSON并生成数组的temp逻辑:

var temp = initialRes.reduce((ar, r) => {
  if (r.getResponseCode() == 200) {
    const data = JSON.parse(r.getContentText());
    // 用可选链+默认值安全获取每个字段,缺失时返回空字符串
    const id = data.id || '';
    const firstName = data.firstName || '';
    const lastName = data.lastName || '';
    const fullName = data.fullName || '';
    const displayName = data.displayName || '';
    const shortName = data.shortName || '';
    const weight = data.weight || '';
    const height = data.height || '';
    // 处理position.abbreviation:position不存在或abbreviation缺失时返回空
    const abbreviation = data.position?.abbreviation || '';
    const dateOfBirth = data.dateOfBirth || '';
    const displayValue = data.hand?.displayValue || '';
    const jersey = data.jersey || '';
    const debutYear = data.debutYear || '';
    const city = data.birthPlace?.city || '';
    const state = data.birthPlace?.state || '';
    const country = data.birthPlace?.country || '';
    const years = data.experience?.years || '';
    const active = data.active || '';

    ar.push([id, firstName, lastName, fullName, displayName, shortName, weight, height, abbreviation, dateOfBirth, displayValue, jersey, debutYear, city, state, country, years, active]);
  }
  return ar;
}, []);

关键修改点

  1. 可选链操作符?.:访问嵌套对象属性时,若上层对象不存在(如data.position为undefined),不会抛出错误,直接返回undefined。
  2. 默认值|| '':将undefined转换为空字符串,确保插入表格时显示为空单元格。
  3. 移除无效的Try/Catch块和message.innerHTML操作,简化逻辑同时保证稳定性。

替换后,无论哪个嵌套字段缺失,代码都会自动插入空单元格并继续处理后续数据,不会终止执行。


内容的提问来源于stack exchange,提问作者DJ Luke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 04:57:02