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
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.
Related
I’m creating a function to search information in a json structure, my function works well in the first level but I have problems to search in the second level. My funtion return all the object json as if the search had never been performed.
this is my code:
json:
let machine = [
{
"sku": "qweert-12",
"type": "aaaa",
"components":[
{
"unit": "mmmm",
"sku": "qwer-12"
},
{
"unit": "llll",
"sku": "qwer-14"
}
]
},
{
"sku": "qmqert-12",
"type": "bbbb",
"components":[
{
"unit": "ssss",
"sku": "qwlr-12"
},
{
"unit": "jjjj",
"sku": "qwuer-14"
}
]
},
]
function
const search= (inputUser, data, setReturninfo) => {
const input = inputUser.target.value();
const result = data.filter((data) => {
let component= data.components;
return Object.keys(component).some((key) => {
return JSON.stringify((data[key])).toLocaleLowerCase().trim.includes(input);
})
});
setReturninfo(result);
}
I appreciate any help.
var searchResults = []; //this array will be filled with results that match the searched key string
function search(jsonObject, key) {
for (let k in jsonObject) {
let innerObject = jsonObject[k];
if (k == key) {
searchResults.push(innerObject);
}
//recursively search for arrays and objects deep inside:
if (innerObject instanceof Array || typeof innerObject == "object") {
search(innerObject, key);
}
}
}
search(machine, "sku");
//searchResults will be: [ "qweert-12", "qwer-12", "qwer-14", "qmqert-12", "qwlr-12", "qwuer-14" ]
Using a recursive function to search nested levels. Hope I got the function you where searching for.
Request you to please help in building a function in javascript to obtain the mentioned output from the input given.
INPUT : An object (possibly a nested object)
example :
{
"message":"string" ,
"data1": {
"Output1": {
"leaf1": "abc",
"Leaf2": "123"
}
}
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
OUTPUT : An array of string
Example :
str= ["message", "data1.Output1.leaf1", "data1.Output1.leaf2" , "data2.Output2.leaf3","data2.Output2.leaf4"]
Something like this it will work
const getBranches = (data, prefix=[]) => {
if (typeof(data) !== 'object') {
return prefix.join('.')
}
return Object.entries(data).flatMap(([k, v]) => getBranches(v, [...prefix, k]))
}
const data = {
"message": "string",
"data1": {
"Output1": {
"leaf1": "abc",
"Leaf2": "123"
}
},
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
console.log(getBranches(data))
Second version
const data = {
"message": "string",
"data1": {
"Output1": {
"leaf1": [{
"b": {
"c": "12"
}
}]
}
},
"data2": {
"Output2": {
"leaf3": "abc",
"leaf4": "123"
}
}
}
const getBranches = (data, prefix = []) => {
if (typeof(data) !== 'object') {
return prefix.join('.')
}
return Object.entries(data).flatMap(([k, v]) => Array.isArray(data) ? getBranches(v, [...prefix]) : getBranches(v, [...prefix, k]))
}
console.log(getBranches(data))
I have an object where at global level I have the changed values and inside one property called initialData I have the initial value, so what I am trying to do is, based on the array mentioned values I need to find whether the initialData has been changed or not.
const eligibleFields = ['address_line_1', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
Here first I need to loop through the mentioned fields like address_line_1 and take the value as Washington, now compare it outer not inside initialData, so outer its Washington DC, so there is a change.
Now I need to return a boolean value.
This is working, but is there a simple way?
const eligibleFields = ['address_line_2', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
function findVal(obj, key) {
var seen = new Set,
active = [obj];
while (active.length) {
var new_active = [],
found = [];
for (var i = 0; i < active.length; i++) {
Object.keys(active[i]).forEach(function(k) {
var x = active[i][k];
if (k === key) {
found.push(x);
} else if (x && typeof x === "object" &&
!seen.has(x)) {
seen.add(x);
new_active.push(x);
}
});
}
if (found.length) return found;
active = new_active;
}
return null;
}
let isChanged = eligibleFields.some(field => {
let initialValue = findVal(object.initialData, field)?.[0]
if (initialValue) {
let changedValue = findVal(object, field)?.[0]
if (changedValue != initialValue) {
console.log("changedValue =>",changedValue, ",", "initialValue =>",initialValue)
return true
}
}
})
console.log(isChanged)
This seems simpler to me:
const eligibleFields = ['address_line_1', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
const recursiveSearch = (obj, searchKey) => {
for (const [key, value] of Object.entries(obj)) {
if (key === searchKey && typeof value !== 'object') {
return value;
} else if (typeof value === 'object') {
return recursiveSearch(value, searchKey);
}
}
return undefined;
};
let isChanged = eligibleFields.some(field => {
if (!(field in object)) return false;
let initialValue = recursiveSearch(object.initialData, field);
let changedValue = object[field];
if (changedValue !== initialValue) {
console.log(`"${field}" was changed from ${initialValue} to ${changedValue}`)
return true
}
})
console.log(isChanged)
This assumes that only initialData has nested values, as the example provided implies. Anyways you should consider changing your JSON schema to match the initial data and the changed data.
If initialData does not have duplicate key names inside, I suggest to flaten the initialData first into an array of key value pairs so you can access it later by key names.
In below code first we are doing the conversion from this
{
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork"
},
"comment": "Initial Data"
}
to this
{
"id": 1,
"is_new": true,
"address_line_1": "Washington",
"address_line_2": "Newyork",
"comment": "Initial Data"
}
by this code
function flatenInitialData(obj, res = {}){
for(let key in obj){
if(typeof obj[key] == 'object'){
flatenInitialData(obj[key], res);
} else {
res[key] = obj[key];
}
}
return res;
}
which goes into deepest level and takes the key value and adds it into a new key and value pair array
finally we just do one for loop only for eligibleFields which loops 2 times
Please note in below example if eligibleFields has a entry such as "is_new" but "is_new" is not in the outer array it will still count it as not changed but you can change this in the condition part inside the for loop code
const eligibleFields = ['address_line_2', "is_new"]
const object = {
"id": "1",
"isGetting": false,
"address_line_1": "Washington DC",
"address_line_2": "Newyork",
"isOpen": false,
"comment": "Changed Data",
"initialData": {
"id": 1,
"is_new": true,
"address": {
"address_line_1": "Washington",
"address_line_2": "Newyork",
},
"comment": "Initial Data"
}
}
function flatenInitialData(obj, res = {}){
for(let key in obj){
if(typeof obj[key] == 'object'){
flatenInitialData(obj[key], res);
} else {
res[key] = obj[key];
}
}
return res;
}
var flatInitialData = flatenInitialData(object["initialData"]);
function isChanged(){
var hasChanged = false;
for(var i = 0; i < eligibleFields.length; i++){
if(object[eligibleFields[i]]){
if(object[eligibleFields[i]] != flatInitialData[eligibleFields[i]]){
hasChanged = true;
}
}
}
return hasChanged;
}
console.log(isChanged())
I have a search input in my angular application that should compare the input data with different object properties
<div class="forms">
<div class="search-wrapper">
<input
class="search"
[ngClass]="{'searching': searching}"
type="text"
(input)="changeSearch($event.target.value)"
/>
<label class="">
<span>Rechercher</span>
</label>
</div>
</div>
the logic I use is as follows
public changeSearch(searchTerm: string) {
this.searching = !!searchTerm;
if (!this.searching) {
this.orders.forEach(order => {
order.show = true;
});
return;
}
const extendSearchIn = ['order_number', 'transaction.item.product.name'];
this.orders.forEach(order => {
order.show = true;
extendSearchIn.forEach(property => {
this.searchByProperty(order, property, searchTerm);
});
});
}
public searchByProperty(order, property, searchTerm) {
const prop = this.getSearchProperty(order, property);
if (prop === undefined) { return false; }
return (<String>prop.toLowerCase()).startsWith(searchTerm.toLowerCase());
}
public getSearchProperty(item: object, property: string) {
let itemCopy = Object.assign({}, item);
let result: any;
const props = property.split('.');
props.forEach(prop => {
if (itemCopy !== undefined) {
itemCopy = itemCopy[prop];
}
});
result = itemCopy !== undefined ? itemCopy : result;
return result;
}
and the structure of each object 'order' is like the following
{
"functional_id": "202006101058160012400000SD4AYAA1",
"transactions": [
{
"quantity": 2,
"price": 140,
"item": {
"name": "Carton",
"description": "+ 2 recharges",
"product": {
"name": "Coffret rouge"
}
},
"amount": 280
},
{
"quantity": 2,
"price": 140,
"item": {
"name": "10 coffrets",
"description": "+ 2 recharges",
"product": {
"name": "Coffret gris"
}
},
"amount": 280
},
{
"quantity": 2,
"price": 60,
"item": {
"name": "PACK N°1 comprenant :",
"description": "6 modèles",
"product": {
"name": "AfuBOX",
"description": "60,8 x 39,5 x 16,5 cm"
}
},
"amount": 120
}
],
"show": true,
"date": "10/06/2020",
"order_number": "105816",
"overallAmount": 680
}
you would need to set the 'show' property to false so that those that don't comply with what was entered in the search field would be hidden
Someone to make me see where my mistake is.
Thank you in advance
I have simplified the logic with a forEach and checking if the value I receive from the input contains any of the search criteria I wanted to apply.
I hope that this will help you to explain if you find yourself in a similar situation.
public changeSearch(searchTerm: string) {
this.searching = !!searchTerm;
if (!this.searching) {
this.orders.forEach(order => {
order.show = true;
});
return;
}
this.orders.forEach(order => {
order.show = true;
this.searchByProperty(order, searchTerm);
});
}
public searchByProperty(order, searchTerm) {
const id = (order.order_number + '');
const amount = (order.overallAmount + '');
order.transactions.forEach(items => {
const title = items.item.product.name.toLowerCase();
if (title.includes(searchTerm) || id.includes(searchTerm) || amount.includes(searchTerm)) {
order.show = true;
} else {
order.show = false;
}
});
}
I am trying to parse the below JSON (format), flatten it out and get the required key / value (output) as mentioned below. I am trying to figure out the best way to do that.
I need to get the displayName and its corresponding parent
For Ex:
Features: taxoFeatures
Input:
{
"Shop": {
"subFilter": [
{
"taxoFeatures": {
"displayName": "Features"
}
},
{
"color_base": {
"displayName": "Colour"
}
}
],
"displayName": "Shopping"
},
"Support": {
"subFilter": [
{
"contentType": {
"displayName": "Content"
}
}
],
"displayName": "Support documents"
}
}
Expected output:
{
"Shopping": "Shop",
"Features":"taxoFeatures",
"Colour":"color_base",
"Content":"contentType",
"Support documents":"Support"
}
I was thinking of looping through the JSON and find the key and add the corresponding key and displayName value (and then loop through each child array and store those values as well). Any other ideas?
let customName = {};
for (const key in filter) {
if (filter.hasOwnProperty(key)) {
const value = filter[key];
if (isNotEmpty(value.displayName)) {
customName[value.displayName] = key;
}
}
}
You could use this recursive function which builds up the result object using reduce and Object.assign:
function transform(filter) {
return Object.keys(filter).reduce( (acc, key) => {
const value = filter[key];
if (Object(value) === value) { // it's an object
if ('displayName' in value) {
acc[value.displayName] = key;
}
Object.assign(acc, transform(value)); // recurse
}
return acc;
}, {});
}
// Sample input
const filter = {
"Shop": {
"subFilter": [
{
"taxoFeatures": {
"displayName": "Features"
}
},
{
"color_base": {
"displayName": "Colour"
}
}
],
"displayName": "Shopping"
},
"Support": {
"subFilter": [
{
"contentType": {
"displayName": "Content"
}
}
],
"displayName": "Support documents"
}
}
console.log(transform(filter));
Well, basically yes. You need to loop it throught, but as u have same code you can make a recursion, something like this:
function doit (json) {
let ret = {};
Object.keys(json).forEach((key) => {
const childs = (!!json[key].subFilter) ? json[key].subFilter.map(doit) : [];
ret = Object.assign(ret, { [json[key].displayName]: key }, ...childs);
});
return ret;
}
Here is another way to do it:
var data = {
"Shop": {
"subFilter": [
{
"taxoFeatures": {
"displayName": "Features"
}
},
{
"color_base": {
"displayName": "Colour"
}
}
],
"displayName": "Shopping"
},
"Support": {
"subFilter": [
{
"contentType": {
"displayName": "Content"
}
}
],
"displayName": "Support documents"
}
};
var res = {};
function search(obj){
for(item in obj) {
res[obj[item].displayName] = item;
if (obj[item].subFilter)
obj[item].subFilter.forEach((subitem)=>search(subitem));
}
}
search(data);
console.log(res);
JSFiddle: https://jsfiddle.net/7b6bcjfk/