How can we flatten XML parsed with fast-xml-parser

2.5k Views Asked by At

Have inherited a Node.js app that needs some maintenance and its not my strong point.

We are parsing XML using fast-xml-parser which works really well with most of our inputs. However we have some inputs that are an extra level deep, and we need to flatten the output to all be same level.

The Input: where Price value is the extra level deep

<products capture-installed="true">
<script/>
<script/>
<script/>
<product>
<pid>8</pid>
<modelno>6273033</modelno>
<name>
<![CDATA[ Big Red Truck ]]>
</name>
<category>
<![CDATA[ Toys]]>
</category>
<currency>USD</currency>
<price>
  <actualprice>19.20</actualprice>
</price>
</product>

When we flatten it with existing code we get:

   "product": {
      "pid": "8",
      "modelno": "6273033",
      "name": "Big Red Truck",
      "category": "Toys",
      "currency": "USD",
      "price": {
         "actualprice": "19.20"
      }

But what we need is something like:

   "product": {
      "pid": "8",
      "modelno": "6273033",
      "name": "Big Red Truck",
      "category": "Toys",
      "currency": "USD",
      "price-actualprice": "19.20"
      }

The current Code:

const parse = require("fast-xml-parser");

const options = {
  ignoreAttributes : true,
  ignoreNameSpace : false,
  parseNodeValue : false,
  tagValueProcessor : a => {
    if(Array.isArray(a)){
      return a.join(',');
    }
    return a;
  }
};

const flatten = (data) => {
  return data.map(row => {
    const fieldNames = Object.keys(row);
    for (const fieldName of fieldNames) {
      if(Array.isArray(row[fieldName])){
        row[fieldName] = row[fieldName].join(',');
      }

      if(typeof row[fieldName] === 'object'){
        row[fieldName] = JSON.stringify(row[fieldName]);
      }
    }
    return row;
  });
};


function findTheArray(o) {
  if(Array.isArray(o)){
    return o;
  }
  var result, p; 
  for (p in o) {
      if( o.hasOwnProperty(p) && typeof o[p] === 'object' ) {
          result = findTheArray(o[p]);
          if(result){
              return result;
          }
      }
  }
  return result;
}


module.exports = function parseData(data) {
  return new Promise((resolve, reject) => {
    try {
      const isValid = parse.validate(data);
      if (isValid === true) {
        const pData = parse.parse(data, options);
        const array = findTheArray(pData);
        if(array){
          resolve(flatten(array));
        } else {
          reject('Can\'t find any goodies!');
        }
      } else {
        reject(isValid.err);
      }
    } catch (err) {
      reject(err);
    }
  });
};

I've worked on the this area of the code but haven't been able to get any success:

if(typeof row[fieldName] === 'object'){
        row[fieldName] = JSON.stringify(row[fieldName])

Ideas? thanks

1

There are 1 best solutions below

0
On

In the recent version of FXP, this is how you can do;

const options = {
    ignoreAttributes: true,
    stopNodes: [
        "products.product.price"
    ],
    tagValueProcessor: (tagName, tagValue, jPath, hasAttributes, isLeafNode) => {
        if (jPath === 'products.product.price') {
            return /([0-9]+\.[0-9]+)/.exec(tagValue)[1]
        }
    },
    // preserveOrder: true,
};
const parser = new XMLParser(options);
let result = parser.parse(XMLdata);

Output

"product": {
    "pid": "8",
    "modelno": "6273033",
    "name": "Big Red Truck",
    "category": "Toys",
    "currency": "USD",
    "price": "19.20"
}

However, tag name can't be changed.