0

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

Mark McP
  • 13
  • 4
  • The "current code" would return a different result than the one shown above - `product.price` is clearly not a string and therefor not JSON. – Andreas Feb 29 '20 at 08:42
  • However the results are showing we are getting: pid = 8 modelno = 6273033 etc price = {"actualprice": "19.20"} This is what the app is giving us in our price field on screen. But we just want price = 19.20 or price-actualprice = 19.20 (doesnt matter what the field name ends up being) } – Mark McP Mar 01 '20 at 13:09

1 Answers1

0

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.

Amit Kumar Gupta
  • 7,193
  • 12
  • 64
  • 90