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);
}
Updated 9 months ago