Step 3: (Optional) Parse the Source File

If the columns and values in your feed file don’t meet the requirements described in Step 1: Create the Feed File, or if some other manipulation is required to duplicate, move around or rename values and fields, you can modify the feed using a parser function created in JavaScript. For example, XML files that are not "flat" can be "flattened" using a parser function.

📌

Parser functions are only supported for data feeds up to 200,000 items and 180 MB.

To add a parser function, go to Advanced Settings › Parser Function and enter your code.

Example: Parse an XML feed

Parser functions are required for XML feeds so the feed can be converted into an array inside a JSON.

function parser(data){
 var products = data.items[0].item;
 return products.map(function(item){
 var newFeedRow = {};
 for(var column in item){
 newFeedRow[column] = item[column][0];
 }
 return newFeedRow;
 });
}

Example: Combine multiple source files

If you combine multiple source files, you must use the parser function to unify them using a unique product identifier that exists in both of the files.

// first paramater with name 'feed1' is default/base feed.
// feed2 and feed3 are in different languages
function parse(feed1, feed2, feed3) {
  const additionalFeeds = {
    de_DE: feed2,
    en_SG: feed3,
  };

  const columnsForAdditionalLocales = [
    'name',
    'price',
    'in_stock',
  ];

  function parseFeed(feed, lng, attributesToInclude = []) {
    return feed.map((item) => Object.entries(item).reduce((acc, [key, value]) => {
      if (attributesToInclude.includes(key)) {
        acc[lng ? `lng:${lng}:${key}` : key] = value;
      } else {
        acc[key] = value;
      }

      return acc;
    }, {}));
  }

  function transformArrayToObjectByKey(array, key) {
    return array.reduce((acc, curr) => {
      acc[curr[key]] = curr;
      return acc;
    }, {});
  }

  const transformedFeeds = Object.entries(additionalFeeds)
    .map(([lng, feedArr]) => transformArrayToObjectByKey(parseFeed(feedArr, lng, columnsForAdditionalLocales), 'sku'));

  return parseFeed(feed1).map((product) => {
    transformedFeeds.forEach((feedArr) => {
      Object.assign(product, feedArr[product.sku]);
    });

    return product;
  });
}
}

Example: Import a Google feed file

GPF (Google Product Feed), commonly used for Shopping Ads, is very similar to the Experience OS product feed structure. If you already have a GPF, you can use the following parser function code as an example, and adjust it as needed.

function parse(data) {
 const DY_GOOGLE_FIELD_MAPPING = {
 'sku': 'g:id',
 'name': 'g:title',
 'url': 'g:link',
 'price': 'g:price',
 'in_stock': 'g:availability',
 'image_url': 'g:image_link',
 'categories': 'g:product_type',
 'group_id': 'g:item_group_id'
 };
 /*
 in case the category is constructed from several feeds with the same name and a number,
 for example: "g:category_level1","g:category_level2","g:category_level3":
 */
 const DY_GOOGLE_FIELD_MAPPING_CATEGORIES_FALLBACK = 'g:category_level*';
 const MAX_FALLBACK_CATEGORIES = 10;
 const TRY_FINDING_ITEMS_IN_XML_AUTOMATICALLY = true;
 const ONLY_ADD_ITEMS_FROM_FIELD_MAPPING = false;
 /* In case the automatic item allocation is not used:*/
 const FEED_ITEMS_ARRAY_PATH = data.rss[0].channel[0].item;

 const parseItem = oldItem => {
 let newItem = {};
 for (let dyFieldName in DY_GOOGLE_FIELD_MAPPING) {
 if (!DY_GOOGLE_FIELD_MAPPING.hasOwnProperty(dyFieldName)) {
 continue;
 }
 switch (true) {
 case dyFieldName === 'sku':
 case dyFieldName === 'name':
 case dyFieldName === 'url':
 case dyFieldName === 'image_url':
 case (dyFieldName === 'group_id' && typeof oldItem[DY_GOOGLE_FIELD_MAPPING.group_id] !== 'undefined'):
 newItem[dyFieldName] = stringifyValue(oldItem, DY_GOOGLE_FIELD_MAPPING[dyFieldName]);
 break;
 /* if no group id found in the original feed, use the sku instead:*/ 
 case (dyFieldName === 'group_id' && typeof oldItem[DY_GOOGLE_FIELD_MAPPING.group_id] === 'undefined'):
 newItem[dyFieldName] = stringifyValue(oldItem, DY_GOOGLE_FIELD_MAPPING.sku);
 break;
 case dyFieldName === 'in_stock':
 newItem[dyFieldName] = stringifyValue(oldItem, DY_GOOGLE_FIELD_MAPPING[dyFieldName]) === 'in stock';
 break;
 case dyFieldName === 'price':
 newItem[dyFieldName] = parseFloat(stringifyValue(oldItem, DY_GOOGLE_FIELD_MAPPING[dyFieldName]).replace(',', '.').replace(/[^0-9\.-]+/g, ''));
 break;
 case (dyFieldName === 'categories' && typeof oldItem[DY_GOOGLE_FIELD_MAPPING.categories] !== 'undefined'):
 newItem[dyFieldName] = stringifyValue(oldItem, DY_GOOGLE_FIELD_MAPPING[dyFieldName]).replace(/\ >\ /g, '|');
 break;
 /* if the category feed doesn't exist, loop through the fallback value*/
 case (dyFieldName === 'categories' && typeof oldItem[DY_GOOGLE_FIELD_MAPPING.categories] === 'undefined'):
 let categoryStr = '';
 for (let i = 0; i < MAX_FALLBACK_CATEGORIES; i++) { let categoryFieldName = DY_GOOGLE_FIELD_MAPPING_CATEGORIES_FALLBACK.replace('*', i); if (oldItem[categoryFieldName] && oldItem[categoryFieldName][0]) { if (categoryStr.length) { categoryStr += '|'; } categoryStr += oldItem[categoryFieldName][0]; // delete oldItem[categoryFieldName]; } if (categoryStr.length && typeof oldItem[categoryFieldName] === 'undefined') { break; } } newItem[dyFieldName] = categoryStr; break; default: } } if (!ONLY_ADD_ITEMS_FROM_FIELD_MAPPING) { for (let customerField in oldItem) { if (!oldItem.hasOwnProperty(customerField)) { continue; } let fieldValue = stringifyValue(oldItem, customerField); if (typeof fieldValue === 'object') { // this currently support nested objects for non mandatory/mapped fields. parseNestedObject(newItem, oldItem, customerField); } else { newItem[customerField] = fieldValue; } } } return newItem; }; /* this funtion scans the xml for array larger than 1, which will most likley only be the array of items. */ const getItemsNode = feed => {
 let elements = [feed];
 for (let maxTries = 100; maxTries > 0; maxTries--) {
 if (!elements.length) {
 break;
 }
 let element = elements.pop();
 if (typeof element !== 'object') {
 continue;
 }
 if (element.length && element.length > 1) {
 return element;
 }
 if (element.length) {
 elements.push(element[0]);
 } else {
 for (let child in element) {
 if (!element.hasOwnProperty(child)) {
 continue;
 }
 elements.push(element[child]);
 }
 }
 }
 return true;
 };

 /* tries to return the value as a string, else return the value as is*/
 const stringifyValue = (oldItem, field) => {
 if (!oldItem[field]) {
 return '';
 }
 let fieldValue = oldItem[field][0];
 if (typeof fieldValue !== 'object') {
 return (fieldValue + '').replace('<![CDATA[', '').replace(']]>', '');
 }
 return fieldValue;
 };

 /*
 some google fields contains nested objects. This only covers one child hirarchy and
 naming the nested field in this patern: parent_child_i for example: 'g:shipping_g:country_1'
 */
 const parseNestedObject = (newItem, oldItem, parentFieldName) => {
 for (let i = 0; i < oldItem[parentFieldName].length; i++) {
 let nestedFieldObject = oldItem[parentFieldName][i];
 for (let nestedField in nestedFieldObject) {
 if (!nestedFieldObject.hasOwnProperty(nestedField)) {
 continue;
 }
 newItem[parentFieldName + '_' + nestedField + '_' + i] = nestedFieldObject[nestedField][0];
 }
 }
 };


 let oldItems;
 if (TRY_FINDING_ITEMS_IN_XML_AUTOMATICALLY) {
 oldItems = getItemsNode(data);
 } else {
 oldItems = FEED_ITEMS_ARRAY_PATH;
 }

 return oldItems.map(parseItem);
}