Converting XML to JSON in JS - javascript

I just recently started learning js and trying to solve the problem of converting xml source file
<ns1:input xmlns:ns1="ns1:test" xmlns:ns2="ns2:test">
<ns1:element id="1">
<ns2:field1>1</ns2:field1>
<ns2:field2>2</ns2:field2>
<ns2:field3>3</ns2:field3>
</ns1:element>
<ns1:element id="2">
<ns2:field1>4</ns2:field1>
<ns2:field2>5</ns2:field2>
<ns2:field3>6</ns2:field3>
</ns1:element>
</ns1:input>
to json of the following content
{
"elements":[
{
"element":{
"field1":"1",
"field2":"2",
"field3":"3",
"id":"1"
}
},
{
"element":{
"field1":"4",
"field2":"5",
"field3":"6",
"id":"2"
}
}
]
}
using javascript.
I tried to solve the problem using the xml2json library - it turned out not quite what I needed. Can you please tell me how this can be implemented?
Below is code and output
var convert = require('xml-js');
var xml =
'<ns1:input xmlns:ns1="ns1:test" xmlns:ns2="ns2:test">'+
'<ns1:element id="1">'+
'<ns2:field1>1</ns2:field1>'+
'<ns2:field2>2</ns2:field2>'+
'<ns2:field3>3</ns2:field3>' +
'</ns1:element>'+
'<ns1:element id="2">'+
'<ns2:field1>4</ns2:field1>'+
'<ns2:field2>5</ns2:field2>'+
'<ns2:field3>6</ns2:field3>'+
'</ns1:element>'+
'</ns1:input>';
var result2 = convert.xml2json(xml, {compact: true, spaces: 2, ignoreAttributes:true});
console.log(result2);
result
{
"ns1:input": {
"ns1:element": [
{
"ns2:field1": {
"_text": "1"
},
"ns2:field2": {
"_text": "2"
},
"ns2:field3": {
"_text": "3"
}
},
{
"ns2:field1": {
"_text": "4"
},
"ns2:field2": {
"_text": "5"
},
"ns2:field3": {
"_text": "6"
}
}
]
}
}

Try lib named x2js
var x2js = new X2JS();
var xmlText = "your_xml_string";
var jsonObj = x2js.xml_str2json( xmlText );
For more documentation refer to github page

Rather then doing a generic convert you can read the XML using DOM+Xpath and build up the JSON. The main benefit is that the JSON structure does not directly depend on the XML structure.
This works in any modern browser, in node.js you need the xmldom and xpath packages.
// provide namespace resolution for the Xpath expressions
const resolver = function(prefix) {
const namespaces = {
n1: 'ns1:test',
n2: 'ns2:test'
}
return namespaces[prefix || ''] || null;
}
const data = new DOMParser().parseFromString(getXMLString(), 'text/xml');
// fetch "{ns1:test}element" nodes
const elementNodes = data.evaluate(
"//n1:element", data, resolver, XPathResult.ANY_TYPE, null
);
let elementNode;
const elements = [];
while (elementNode = elementNodes.iterateNext()) {
const element = {
// read the id attribute
id: elementNode.getAttribute('id'),
// fetch the text contents of the first "{ns2:test}field1"
// note the string cast in the expression
field1: data.evaluate(
'string(n2:field1)', elementNode, resolver, XPathResult.STRING_TYPE, null
).stringValue,
field2: data.evaluate(
'string(n2:field2)', elementNode, resolver, XPathResult.STRING_TYPE, null
).stringValue,
field3: data.evaluate(
'string(n2:field3)', elementNode, resolver, XPathResult.STRING_TYPE, null
).stringValue
}
elements.push({element});
}
console.log({elements});
function getXMLString() {
return `<ns1:input xmlns:ns1="ns1:test" xmlns:ns2="ns2:test">
<ns1:element id="1">
<ns2:field1>1</ns2:field1>
<ns2:field2>2</ns2:field2>
<ns2:field3>3</ns2:field3>
</ns1:element>
<ns1:element id="2">
<ns2:field1>4</ns2:field1>
<ns2:field2>5</ns2:field2>
<ns2:field3>6</ns2:field3>
</ns1:element>
</ns1:input>
`;
}

Related

How to construct JSON dynamically from array list of elements?

I have a list of elements in an array: empIds: [38670, 38671, 38672, 38673]
I am trying to build a JSON that holds all these array elements in the payload:
{
"members": [
{
"EmployeeId": "38670"
},
{
"EmployeeId": "38671"
},
{
"EmployeeId": "38672"
},
{
"EmployeeId": "38673"
}
]
}
I wasn't completely sure as I am trying to get my head around. Below is my incomplete implementation:
`
let parts = [];
for(i=0;i<memberInternalIds.length; i++){
if(memberInternalIds.length == 1){
parts ={participantId: memberInternalIds[0]}
} else {
parts ={participantId: memberInternalIds[i]}
}
}
`
Not sure how to dynamically create JSON structure with followed by comma-separated key/values.
const empIds = [38670, 38671, 38672, 38673];
let payload = { members: empIds.map(id => ({"EmployeeId": id})) };
// Convert the payload to a JSON string
const jsonStr = JSON.stringify(payload);
// Print the JSON string
console.log(jsonStr);

How to get JSON data with changing part of the call to get that data

I'm working on a side project of a currency converter. I've made an API Call and this is all the data it sent me (this varies depending on the selected currencies so USD_GBP could be CAD_JPY):
{
"query": {
"count": 1
},
"results": {
"USD_GBP": {
"id": "USD_GBP",
"val": 0.733695,
"to": "GBP",
"fr": "USD"
}
}
}
In my express code I've set up the HTTPS Module:
https.get(url, function(response) {
response.on("data", function(data) {
const currencyData = JSON.parse(data);
const exchangeRate = currencyData.results.USD_GBP.val;
});
});
});
The problem you can probably see is that since the currency to convert from and convert to are always changing the constant exchangeRate doesn't work for any scenario besides converting from USD to GBP. So I'm wondering how to make that work for every combination that I throw at it. Above my HTTPS get request I already made a variable (const combine = firstCurrency + "_" + secondCurrency;) that combines the selections. I tried concatenating it to the exchangeRate variable, but it gave me an error that it can't find the value of undefined.
JavaScript objects allow you to use bracket notation for dynamic variables names.
var obj = { bar: 'baz'; }
var foo = 'bar';
obj.foo ; // This is invalid, as it looks for the "foo" property
obj[foo]; // This is valid, as it looks for the "bar" property we want
Since you mentioned you have a variable named combine already, you should be able to use bracket notation on the results:
const exchangeRate = currencyData.results[combine].val;
Here's a quick little snippet:
var firstCurrency = 'USD';
var secondCurrency = 'GBP';
const combine = firstCurrency + "_" + secondCurrency;
var response = `{
"query": {
"count": 1
},
"results": {
"USD_GBP": {
"id": "USD_GBP",
"val": 0.733695,
"to": "GBP",
"fr": "USD"
}
}
}`;
const currencyData = JSON.parse(response);
const exchangeRate = currencyData.results[combine].val;
console.log( exchangeRate );
If you didn't have the combine variable, you could always use Object.keys() to get the keys from the result and use that, but it's arguably uglier (especially since you already know the key):
const exchangeRate = currencyData.results[Object.keys(currencyData.results)[0]].val;
Instead of currencyData.results.USD_GBP.val, use currencyData.results['USD_GBP'].val
You can even use a variable as well. :
currencies = ['USD_GBP', 'EUR_USD']
currenceis.forEach(currency => {
currencyData.results[currency].val
}
Changed Example:
https.get(url, function(response) {
response.on("data", function(data) {
const currencyData = JSON.parse(data);
const exchangeRate = currencyData.results['USD_GBP'].val;
});
});
});
You can use the backtick syntax.
let firstCurrency = "USD";
let secondCurrency = "GBP";
const currencyData = {
"query": {
"count": 1
},
"results": {
"USD_GBP": {
"id": "USD_GBP",
"val": 0.733695,
"to": "GBP",
"fr": "USD"
}
}
};
const exchangeRate = currencyData.results[`${firstCurrency}_${secondCurrency}`].val;
console.log(exchangeRate);

Make a nested loop for a JSON object in nodejs

Hi I'm new to React and nodejs. I get from the user his preferences for certain categories in the json code for example:
{
"sport" : {
"tennis": "5",
"running": "4",
"swimming": "5"
},
"study" : {
"history" : "0"
}
}
I want for each preference to create its own field in the "userPreferences" object.
This is the code I wrote down but I can not see what the error is here
exports.reduceUserPreferences = (data) => {
let userPreferences = {};
data.forEach(category => {
category.forEach(preference => {
category_preference_name = category.string + "_" + preference.string;
if (!isEmpty(preference.trim())) userPreferences.category_preference_name = preference;
});
});
//if (!isEmpty(data.tennis.trim())) userPreferences.sport_tennis = data.tennis;
//if (!isEmpty(data.swimming.trim())) userPreferences.sport_swimming = data.swimming;
//if (!isEmpty(data.running.trim())) userPreferences.sport_running = data.running;
//if (!isEmpty(data.history.trim())) userPreferences.study_history = data.history;
return userPreferences;
};
I want the "" object to have fields of all preferences along with the category to which they belong.
I can not understand what I am doing wrong, I linger on this code for several hours.
add example
I have another function similar to this function, the input is similar and the output is similar.
For example input:
{
"bio": "hello there",
"website": "",
"location": "los angles"
}
Example function:
exports.reduceUserDetails = (data) => {
let userDetails = {};
if (!isEmpty(data.bio.trim())) userDetails.bio = data.bio;
if (!isEmpty(data.website.trim())) {
// https://website.com
if (data.website.trim().substring(0, 4) !== 'http') {
userDetails.website = `http://${data.website.trim()}`;
} else userDetails.website = data.website;
}
if (!isEmpty(data.location.trim())) userDetails.location = data.location;
return userDetails;
};
The output will be:
An object with the attribute of all preferences along with their value.
I was looking for examples with a nested loop, I could not find.
There are a couple of things you need to fix in your code.
First, when using a variable name as the key to extract from an object, user obj[varName], not obj.varName.
(read more here: Dynamically access object property using variable)
Also, you're trying to loop an Object, not an array. To loop through the keys, use Object.keys()
Combining these two things you get the desired result, as you can see in this snippet. Also, for future questions, I highly recommend you make a snippet yourself.
const jsonOriginal = {
"sport" : {
"tennis": "5",
"running": "4",
"swimming": "5"
},
"study" : {
"history" : "0"
}
}
const reduceUserPreferences = (data) => {
let userPreferences = {};
Object.keys(data).forEach(category => {
Object.keys(data[category]).forEach(preference => {
category_preference_name = category + "_" + preference;
const preferenceValue = data[category][preference].trim();
if (preferenceValue !== '') userPreferences[category_preference_name] = preferenceValue;
});
});
return userPreferences;
};
console.log(reduceUserPreferences(jsonOriginal))

How do I sort a JSON by Date?

I'm trying to loop through a JSON and sort it by the date so I can see the latest date to the oldest date, and then write it to the file.
Here is my code
var reader = JSON.parse(fs.readFileSync('txt.json', 'utf8'));
function sortByDate(a, b) {
return new Date(a.lastUpdated).toJSON() - new Date(b.lastUpdated).toJSON();
}
reader.sort(sortByDate)
JSON Data Example
{
"Data": {
"Contents": [
{
"Key": [
"HelloTest"
],
"lastUpdated": [
"2019-10-25T10:30:50.558Z"
]
},
{
"Key": [
"TestHello"
],
"lastUpdated": [
"2019-03-26T10:30:50.558Z"
]
}
]
}
}
Here are a couple of errors I found in your code:
Your function name has a typo, it should be sortByDate and not sortbyDate.
You need top sort the inner json.Data.Contents array, not the outer json object.
You need to reference the first element of your lastUpdated arrays using lastUpdated[0].
Finally, you do not need to call toJSON() on the date objects in your sorting function, simply convert to date and return the difference.
Also your inner data fields are arrays, which seems strange for a Key and a lastUpdated value.
If you keep your fields as arrays, here is a working example showing how to sort the inner Data.Contents array by date:
const jsonString = `{
"Data": {
"Contents": [{
"Key": ["HelloTest"],
"lastUpdated": ["2019-10-25T10:30:50.558Z"]
}, {
"Key": ["TestHello"],
"lastUpdated": ["2019-03-26T10:30:50.558Z"]
}]
}
}`;
function sortByDate(a, b) {
return new Date(a.lastUpdated[0]) - new Date(b.lastUpdated[0]);
}
const json = JSON.parse(jsonString);
const defaultValue = { Data: { Contents: [] } };
const sortedContents = [...(json || defaultValue).Data.Contents].sort(sortByDate);
const output = { ...json, Data: { Contents: sortedContents } };
console.log(output);
If you change your fields to scalars, which I suggest, here is another example:
const jsonString = `{
"Data": {
"Contents": [{
"Key": "HelloTest",
"lastUpdated": "2019-10-25T10:30:50.558Z"
}, {
"Key": "TestHello",
"lastUpdated": "2019-03-26T10:30:50.558Z"
}]
}
}`;
function sortByDate(a, b) {
return new Date(a.lastUpdated) - new Date(b.lastUpdated);
}
const json = JSON.parse(jsonString);
const defaultValue = { Data: { Contents: [] } };
const sortedContents = [...(json || defaultValue).Data.Contents].sort(sortByDate);
const output = { ...json, Data: { Contents: sortedContents } };
console.log(output);
It looks like you're reading contents from a file, then needs to sort it by date, and then finally write it to a new file. If that is what you're going for, the following should help:
const fs = require('fs');
const path = require('path');
// JSON files can be requied in as objects without parsing
// `arrayOfStuff` will be the var name for `Contents`
const { Data: { Contents: arrayOfStuff } } = require('./data.json');
function sortByDate(el1, el2) {
// access the date from the object and turn into date object
let date1 = new Date(el1.lastUpdated[0]);
let date2 = new Date(el2.lastUpdated[0]);
// compare date objects in revers order to get newest to oldest
return (date2 - date1);
}
// sort `Contents` from the `Data` object and turn into JSON
const sortedContent = arrayOfStuff.sort(sortByDate);
const newDataObj = JSON.stringify({ Data: { Content: sortedContent }}, null, 2);
// create the fully qualified file path with `sortedByDate.json` as the file name
const filePath = path.resolve('./', 'sortedByDate.json');
// write to new file
fs.writeFile(filePath, newDataObj, (err) => {
if(err) {
console.log('Made an oopsie:', err);
}
console.log(`Success!, new JSON file located at: ${filePath}`);
}); // write to file

How to iterate through complex Json object and do something on each property which equals a certain value

I got the following problem,
I need to iterate through a big Json object ( child nodes consist of array's, strings and objects with at least 4-5 layers of depth in terms of nested properties ).
In some parts across the big Json file there is a specific object structure, it has a property named "erpCode". I need to scan the Json and find all the objects with that property, take the value use that code to ask a different API for details and once I get the details insert them into the object with the current 'erpCode'.
Just to clarify, in my case the parent node property name in the Json always equals the value in 'typeSysname' field which located on the same 'level' as the erpCode property.
A simple example :
{
"cars": [
{
"name": "X222",
"carType": {
"erpCode": "skoda",
"value": null,
"typeSysName": "carType"
}
}
],
"model": {
"year": 1999,
"details": {
"erpCode": "112"
"value": null,
"typeSysName": "details"
}
}
}
In this example I need to find 2 properties get the values skoda and 112 out of them and get the value and description data from a different API and set it into this Json in the right location.
P.S. Any chance there is a good npm package which can help me with that?
Edit:
I got a solution in C# from a few months ago which runs in a generic way on the Json and handles the complexity of the structure in a generic way.
But I now need to convert this into Javascript and I am a bit lost.
public static string TranslateDocErpCodes(string jsonString, string topRetailerSysName)
{
try
{
var doc = JObject.Parse(jsonString);
var erpCodeList = doc.SelectTokens("$..erpCode").ToList();
foreach (var erpCodeJToken in erpCodeList)
{
var value = erpCodeJToken?.Value<string>();
var erpCodeParent = erpCodeJToken?.Parent.Parent;
var erpCodeProperty = erpCodeParent?.Path.Split(".").Last();
var result =
_dataService.GetLovFromErpCode(topRetailerSysName, erpCodeProperty, value);
if (result == null)//reset lov obj
{
if (erpCodeParent?.Parent is JProperty prop)
prop.Value = JObject.FromObject(new LovObject { ErpCode = value });
}
else//set lov obj
{
result.ErpCode = value;
if (erpCodeParent?.Parent is JProperty prop)
prop.Value = JObject.FromObject(result);
}
}
return JsonConvert.SerializeObject(doc);
}
catch (Exception e)
{
throw new Exception("ErpConvert.TranslateDocErpCodes() : " + e);
}
}
mb something like;
function processObject(jsonData) {
for (prop in jsonData) {
if (jsonData.hasOwnProperty(prop)) {
// We get our prop
if (prop === 'code') {
let codeValue = jsonData[prop]
doSomeAsync(codeValue)
.then(response => {
jsonData[prop] = response;
})
}
let curValue = jsonData[prop];
if (Array.isArray(curValue)) {
// Loop through the array, if array element is an object, call processObject recursively.
processArray(curValue);
} else if (typeof curValue === 'object') {
processObject(curValue);
}
}
}
}
I took the answer from Aravindh as a starting point and managed to reach what seems to be a complete solution.
I will share it here,
async function convertErpCodes(jsonData, orgName, parentPropertyName){
for (let prop in jsonData) {
if (jsonData.hasOwnProperty(prop)) {
if (prop === 'erpCode') {
const erpCodeValue = jsonData[prop]
const req = {"query": {"erpCode": erpCodeValue, "orgName": orgName, "typeSysName": parentPropertyName}};
const result = await viewLookupErpService.findOne(req);
if(result)
return result;
}
const curValue = jsonData[prop];
if (Array.isArray(curValue)) {
for(let i in curValue){
const res = await convertErpCodes(curValue[i], orgName, prop);
}
} else if (curValue && typeof curValue === 'object') {
const response = await convertErpCodes(curValue, orgName, prop);
if(response){
jsonData[prop] = response;
}
}
}
}
}
P.S.
I set up the values only if I get a response from the third party API ( this is the reason for the result and response logic in the recursion.
I'd use object-scan and lodash.set in combination
// const objectScan = require('object-scan');
// const lodash = require('lodash');
const stats = { cars: [{ name: 'X222', carType: { erpCode: 'skoda', value: null, typeSysName: 'carType' } }], model: { year: 1999, details: { erpCode: '112', value: null, typeSysName: 'details' } } };
const entries = objectScan(['**.erpCode'], { rtn: 'entry' })(stats);
console.log(entries);
// => [ [ [ 'model', 'details', 'erpCode' ], '112' ], [ [ 'cars', 0, 'carType', 'erpCode' ], 'skoda' ] ]
// where you would query the external api and place results in entries
entries[0][1] = 'foo';
entries[1][1] = 'bar';
entries.forEach(([k, v]) => lodash.set(stats, k, v));
console.log(stats);
// => { cars: [ { name: 'X222', carType: { erpCode: 'bar', value: null, typeSysName: 'carType' } } ], model: { year: 1999, details: { erpCode: 'foo', value: null, typeSysName: 'details' } } }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
<script src="https://bundle.run/lodash#4.17.20"></script>
Disclaimer: I'm the author of object-scan

Categories