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

动态API调用优化问询:Fetch函数下产品属性空值处理

优化Alkemics API产品数据获取:处理属性缺失与动态调用

问题背景

我通过Fetch函数调用Alkemics平台API,以GTIN为检索键获取特定品类产品的多属性数据。但部分产品存在属性缺失情况:要么字段存在但值为null,要么平台未填写对应字段。目前脚本里的空值处理逻辑无法完全生效,希望优化实现:

  • 动态API调用,仅获取存在的属性
  • 空值或缺失字段不阻断API调用流程

优化方案与代码实现

1. 新增安全属性访问工具函数

编写通用函数处理嵌套属性的安全访问,替代零散的try/catch和null判断,避免因字段缺失导致的流程中断:

// 安全访问嵌套属性,避免字段缺失或null导致报错
function getSafeValue(obj, path, defaultValue = ' ') {
  return path.split('.').reduce((current, key) => {
    // 处理数组索引,比如namePublicLong[0]
    const arrayMatch = key.match(/(\w+)\[(\d+)\]/);
    if (arrayMatch) {
      const arrKey = arrayMatch[1];
      const index = parseInt(arrayMatch[2], 10);
      return current && current[arrKey] && current[arrKey][index] !== undefined 
        ? current[arrKey][index] 
        : defaultValue;
    }
    return current && current[key] !== undefined ? current[key] : defaultValue;
  }, obj);
}

2. 重构字段映射逻辑

将列名与API字段的映射关系抽离成配置对象,便于维护,同时自动适配属性存在性:

// 列名与API字段的映射配置
const fieldMappings = [
  { column: "GTIN", path: "gtin" },
  { column: "Libelle long", path: "namePublicLong[0].data", defaultValue: "" },
  { column: "Fournisseur", path: "isSharedFrom[0].sourceOrganization.nameLegal" },
  { column: "Catégorie du produit", path: "kind.label" },
  { column: "Code Tiers", path: "supplierId" },
  { column: "Cycle de vie", path: "lifeCycle" },
  { column: "Maturité commande Galec", path: "business_processes.label:Maturité commande Galec", type: "maturity" },
  { column: "Maturité direct magasin", path: "business_processes.label:Maturité direct magasin", type: "maturity" },
  { column: "Maturité Commande SCABEL", path: "business_processes.label:Maturité Commande SCABEL", type: "maturity" },
  { column: "Maturité complémentaire Galec", path: "business_processes.label:Maturité complémentaire Galec", type: "maturity" },
  { column: "Maturité complémentaire Direct Magasin", path: "business_processes.label:Maturité complémentaire Direct Magasin", type: "maturity" },
  { column: "Maturité complémentaire Scabel", path: "business_processes.label:Maturité complémentaire Scabel", type: "maturity" },
  { column: "Maturité Animation Digitale", path: "business_processes.label:Maturité Animation Digitale", type: "maturity" },
  { column: "Maturité Digitale", path: "business_processes.label:Maturité Digitale", type: "maturity" },
  { column: "Maturité Entité Leclerc", path: "business_processes.label:Maturité Entité Leclerc", type: "maturity" },
  { column: "Type d'unité", path: "typePackaging.label" },
  { column: "Marque Commerciale", path: "brand.description" },
  { column: "Appellation viticole ou région d'origine", path: "alcoholicBeverageSubregionText" },
  { column: "Complément d'appellation(N)", path: "alcoholicBeverageAdditionalAppellationDetailsText" },
  { column: "Packaging vin", path: "leclercWinePackagingTypeCode.label" },
  { column: "Contenu net", path: "netWeight[0].data" },
  { column: "Description des saveurs du vin", path: "wineTastingDescriptionList[0].wineTastingCode.label" },
  { column: "Type de champagne(N)", path: "wineCategoryCode.label" },
  { column: "Viticulture(N)", path: "vintnerTextList" },
  { column: "Couleur du Vin", path: "colorGroup.label" },
  { column: "Millésime", path: "vintage" },
  { column: "Terroirs et sol(N)", path: "vineyardRegion.label" },
  { column: "Région Viticole", path: "leclercFrenchVineyardRegionCode.label" },
  { column: "Durée du potentiel de maturation", path: "wineAgeingPotentialDurationCode.label" },
  { column: "Echelle de fruit", path: "wineFruitExpressionNoteNumber" },
  { column: "Echelle de personnalité", path: "wineAromasExpressionNoteNumber" },
  { column: "Dégustation robe", path: "wineTastingRobeDescriptionText[0].data" },
  { column: "Dégustation nez", path: "wineTastingNoseDescriptionText[0].data" },
  { column: "Type de vendange(N)", path: "grapeHarvestingDescriptionText.data" },
  { column: "Dégustation bouche", path: "wineTastingMouthDescriptionText[0].data" },
  { column: "Type d'appellation du vin", path: "wineCategoryCode.label" },
  { column: "Allergènes avérés", path: "allergens[0].data" },
  { column: "Type d'allergène", path: "allergenTypeList" },
  { column: "Degré d'alcool", path: "ratioAlcohol" },
  { column: "Teneur en sucre", path: "sugarContent.label" },
  { column: "Description du cépage", path: "varietalDesignationDescriptionText[0].data" },
  { column: "Accords mets et vins", path: "pairingsTypes[0].pairingsType.label" },
  { column: "Occasions particulières", path: "occasions[0].occasion.label" },
  { column: "Températures de dégustation (en °c)", path: "tempConsumption[0].data" },
  { column: "Potentiel de garde", path: "wineAgeingPotentialDurationCode.label" },
  { column: "Code de nomenclature douanière", path: "importEuropeanClassification" },
  { column: "Code produit accise france", path: "cpaFranceCode.label" },
  { column: "Libellé fiscal", path: "taxLabelList[0].taxLabelCode.label" },
  { column: "Pays d'origine", path: "countryOfOriginList[0].countryOfOriginCode.label" },
  { column: "Type de paiement des droits d'alcools", path: "alcoholicBeveragePaymentTypeCode.label" },
  { column: "Image Face du produit", path: "assets.pictures[0].exportables[0].uniformResourceIdentifier", defaultValue: "" },
  { column: "Image Arrière du produit", path: "assets.pictures[2].exportables[0].uniformResourceIdentifier", defaultValue: "" }
];

3. 优化主函数逻辑

整合工具函数与映射配置,实现动态属性获取与容错处理:

function GetAllProduct() {
  var token = ConnectAlkemics();

  var headers = {
    "accept": "*/*",
    "accept-language": "fr",
    'Authorization': 'Bearer ' + token,
    'cache-control': 'no-cache',
    'content-type': 'application/json;charset=UTF-8',
    'expires': '-1',
    'pragma': 'no-cache',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-site',
    'x-alk-priority': '9'
  };

  var options = {
    'method': "GET",
    "headers": headers
  };

  var source = SpreadsheetApp.getActive().getSheetByName("GTIN");
  var ss = SpreadsheetApp.getActive().getSheetByName("RESULTATS");

  ss.clear();

  // 获取属性列表
  var lastRow = source.getLastRow();
  var listeAttribut = "";
  var tabChamp = [];
  var index = 0;
  for (var i = 2; i <= lastRow; i++) {
    if (source.getRange(i, 3).getValue() != "") {
      tabChamp[index] = source.getRange(i, 2).getValue();
      index += 1;
      listeAttribut = listeAttribut ? `${listeAttribut},${source.getRange(i, 3).getValue()}` : source.getRange(i, 3).getValue();
    }
  }

  // 写入表头
  const columnNames = fieldMappings.map(mapping => mapping.column);
  for (let i = 0; i < columnNames.length; i++) {
    ss.getRange(1, i + 1).setValue(columnNames[i]);
  }

  var total = 0;
  var lignegtin = 1;
  var moregtin = true;

  while (moregtin) {
    var fini = false;
    var nbgtin = 0;
    var lesgtin = "";
    var produits = [];

    // 批量获取50个GTIN
    while ((source.getRange(lignegtin, 1).getValue() != "") && (nbgtin < 50)) {
      lesgtin = lesgtin ? `${lesgtin},${source.getRange(lignegtin, 1).getValue().toString().replace(/\s+/g, " ").trim()}` : source.getRange(lignegtin, 1).getValue().toString().replace(/\s+/g, " ").trim();
      lignegtin++;
      nbgtin++;
    }

    if (!lesgtin) {
      moregtin = false;
      fini = true;
    } else {
      var limit = 200;
      var next_page = '';

      var params = `&filter_product_languages=fr&filter_gtins_in=${lesgtin}&filter_source_include=${listeAttribut}`;
      
      while (!fini) {
        var lesparams = `${params}&limit=${limit}&next_page=${next_page}&ts=${Math.floor((Math.random() * 10000000) + 1)}`;
        var url = `https://apis.alkemics.com/public/v1/products?${lesparams}`;
        
        try {
          var content = UrlFetchApp.fetch(url, options);
          if (content.getResponseCode() == 200) {
            var retour = JSON.parse(content.getContentText());
            next_page = retour.next_page;
            var data = retour.data;
            
            // 处理每个产品数据
            data.forEach(produit => {
              // 预处理成熟度数据
              const maturiteMap = {};
              if (produit.business_processes && Array.isArray(produit.business_processes)) {
                produit.business_processes.forEach(item => {
                  maturiteMap[item.label] = item.requested ? (item.status ? "OK" : "KO") : "";
                });
              }

              // 构建产品行数据
              const ligne = fieldMappings.map(mapping => {
                if (mapping.type === "maturity") {
                  // 处理成熟度类型字段
                  const label = mapping.path.split(':')[1];
                  return maturiteMap[label] || ' ';
                }
                // 安全获取属性值
                return getSafeValue(produit, mapping.path.split(':')[0], mapping.defaultValue || ' ');
              });

              produits.push(ligne);
              total++;
            });

            // 没有下一页则结束循环
            if (!next_page) {
              fini = true;
            }
          } else {
            Logger.log(`API请求失败,响应码:${content.getResponseCode()}`);
            fini = true;
          }
        } catch (e) {
          Logger.log(`API调用报错:${e.message}`);
          fini = true;
        }
      }
    }

    // 将批量数据写入表格
    if (produits.length > 0) {
      ss.getRange(ss.getLastRow() + 1, 1, produits.length, produits[0].length).setValues(produits);
    }
  }
}

// 安全访问嵌套属性,避免字段缺失或null导致报错
function getSafeValue(obj, path, defaultValue = ' ') {
  return path.split('.').reduce((current, key) => {
    // 处理数组索引,比如namePublicLong[0]
    const arrayMatch = key.match(/(\w+)\[(\d+)\]/);
    if (arrayMatch) {
      const arrKey = arrayMatch[1];
      const index = parseInt(arrayMatch[2], 10);
      return current && current[arrKey] && current[arrKey][index] !== undefined 
        ? current[arrKey][index] 
        : defaultValue;
    }
    return current && current[key] !== undefined ? current[key] : defaultValue;
  }, obj);
}

优化说明

  • 安全属性访问:通过getSafeValue函数统一处理所有嵌套属性的访问,避免因字段缺失或null导致的脚本中断。
  • 动态映射配置:字段映射集中管理,新增或修改属性只需调整配置对象,无需修改业务逻辑。
  • 批次容错处理:单个API请求或产品处理报错时,仅记录日志并跳过当前批次,不终止整个流程。
  • 批量写入优化:将产品数据批量收集后再写入表格,减少Spreadsheet API调用次数,提升性能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 19:55:17