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); }

Did this page help you?