JSON to XML conversion in Node JS service (using xml2js) - node.js

I need some help/advice with JSON to XML conversion in Node js.
I have a service that gets a JSON object in request body that needs to convert to XML. I am able to achieve this using node-xml2js for json inputs with maximum one level of nested objects. But, it gets way more complicated with nested objects having attribute values. Attributes should be identified first, prefixed with $ sign and enclosed in curly braces before parsing through xml2js to get correct xml.
Is there a better way of doing this where this complicated layer of reformatting the json input can be simplified?
xml2js can converts this:
{
"Level1":{ "$":{ "attribute":"value" },
"Level2": {"$":{"attribute1":"05/29/2020",
"attribute2":"10","attribute3":"Pizza"}}
}
to this:(which is correct):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Level1 attribute="value">
<Level2 attribute1="05/29/2020" attribute2="10" attribute3="Pizza"/>
</Level1>
But actual json input is this:
{
"Level1":{"attribute":"value",
"Level2": {"attribute1":"05/29/2020",
"attribute2":"10","attribute3":"Pizza"} }
}
Expected same result:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Level1 attribute="value">
<Level2 attribute1="05/29/2020" attribute2="10" attribute3="Pizza"/>
</Level1>
Please let me know if you have worked on similar requirements. Appreciate any help.
Thank you.

This would be a way to change the object back to the format expected in the library, although it assumes that all non object keys are supposed to be attributes (is that a valid assumption for your application?)
function groupChildren(obj) {
for(prop in obj) { // consider filtering for own properties (vs from prototype: for(prop of Object.keys(obj)) {
if (typeof obj[prop] === 'object') {
groupChildren(obj[prop]);
} else {
obj['$'] = obj['$'] || {};
obj['$'][prop] = obj[prop];
delete obj[prop];
}
}
return obj;
}
and then used like so:
var xml2js = require('xml2js');
var obj = {
Level1: {
attribute: 'value',
Level2: {
attribute1: '05/29/2020',
attribute2: '10',
attribute3: 'Pizza'
}
}
};
var builder = new xml2js.Builder();
var xml = builder.buildObject(groupChildren(obj));
which prints out:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Level1 attribute="value">
<Level2 attribute1="05/29/2020" attribute2="10" attribute3="Pizza"/>
</Level1>

you can use this library :nashwaan/xml-js
Like This:
let xmlJ=require('xml-js');
let parseToJson=(xml)=>{
return new Promise(resolve => {
let convert;
convert=xmlJ.xml2json(xml,{compact:true});
resolve(convert);
});
};

Related

NodeJS checking if XML element exists and add or delete

I'm working with XML in NodeJS. I've been using the xmlbuilder to create my XML. The problem is that now I need to check if a element already exists and delete or update it.
For example, I have the following XML
<?xml version="1.0"?>
<listings>
<listing>
<id>1</id>
<name>TEST</name>
<description>TEST</description>
</listing>
<listing>
<id>2</id>
<name>TEST</name>
<description>TEST</description>
</listing>
</listings>
Then, I call my updateXML controller to add data to it.
const builder = require('xmlbuilder');
const fs = require('fs');
const path = require("path");
exports.updateXML = async (req, res, next) => {
const data = req.body.data;
/*
For example data is
{
id: 2,
name: "Test2",
description: "Desc2"
}
*/
const xmlFile = fs.readFileSync(path.resolve(__dirname, "./oodle.xml"), 'utf8');
if(/*How do I check if the xmlFile has a <id> === data.id?*/) {
// If id matches. How can I delete the whole <listing> node for that id?
}
const newListing = builder.create('listing');
newListing.ele("id", data.id);
newListing.ele("name", data.name);
newListing.ele("description", data.description);
// How can I add the newListing node to the xmlFile?
}
Thanks
I don't believe it's really necessary to remove the node with the duplicate <id>, create a new <listing> node and then insert it into the xml. At least as far as the sample xml in your question is concerned, you can just modify the text child nodes of the relevant <listing> node.
Something along the lines of:
const { select } = require('xpath');
let query = `//listing[./id[./text()="${data.id}"]]`;
const nodes = select(query, doc.node);
nodes.forEach(function (node) {
nam = select('.//name/text()',node)
desc = select('.//description/text()',node)
nam[0].data = data.name;
desc[0].data = data.description;
});
const serializedXML = doc.end({ format: 'xml', prettyPrint: true });
console.log(serializedXML)
Output:
<?xml version="1.0"?>
<listings>
<listing>
<id>1</id>
<name>TEST</name>
<description>TEST</description>
</listing>
<listing>
<id>2</id>
<name>Test2</name>
<description>Desc2</description>
</listing>
</listings>

Node.js JSON to XML - serialize a complex object

I'm attempting to convert a complex JSON object to XML using this package - XML.
var xml = require("xml");
var obj = { MS : { ts : 3423523, isOk : false , errors : [] }};
var xmlObj = xml(obj); // This outputs <MS/>
Any ideas how to make the XML parser go deeper? Why is it prematurely closed?
You could give the xml2js module a try, this would convert your object to Xml quite easily, e.g.
const xml2js = require('xml2js');
const obj = { MS : { ts : 3423523, isOk : false , errors : [] }};
const builder = new xml2js.Builder( { headless: false, renderOpts: { pretty: true } });
const xml = builder.buildObject(obj);
console.log(xml)
The output would be:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<MS>
<ts>3423523</ts>
<isOk>false</isOk>
</MS>

Cannot access json values after converting from xml using xml-js parser

I parse the xml using the following code
var convert = require('xml-js');
var xml = require('fs').readFileSync('./2B2DE7DD-FD11-4F2C-AF0D-A244E5977CBA.xml', 'utf8');
result = convert.xml2json(xml, { spaces: 4});
The result throws the following JSON
{
"declaration": {
"attributes": {
"version": "1.0",
"encoding": "utf-8"
}
}
}
However if i try accesing "declaration" using result["declaration"]the console returns undefined
Should i use another parser or is there something wrong with getting the value.
Please use xml2js instead of xml2json if you want it return object.
result = convert.xml2js(xml, options); // to convert xml text to javascript object
result = convert.xml2json(xml, options); // to convert xml text to json text
The data type of result is String, not JavaScript object. That is, the convert.xml2json(xml, { spaces: 4}); statement will return a JSON String, not JS object.
To access declaration, you need to parse the JSON string to object:
var convert = require('xml-js');
var xml = require('fs').readFileSync('./2B2DE7DD-FD11-4F2C-AF0D-A244E5977CBA.xml', 'utf8');
result = convert.xml2json(xml, { spaces: 4});
result = JSON.parse(result);

Extracting Extended Data from KML using Javascript

I have a placemark in a KML that looks something like this
<Placemark>
<id>test345</id>
<name>Images from KML file</name>
<ExtendedData>
<Data name="type">
<value>images</value>
</Data>
</ExtendedData>
<Point>
<coordinates>-122.448425,37.802907,0</coordinates>
</Point>
I'm attempting to extract the ExtendedData information out of this placemarker on a click event:
google.earth.addEventListener(kmlObject, 'click', function(event) {
event.preventDefault();
var kmlPlacemark = event.getTarget();
});
An alternative solution would be to get the kmlObject from the kmlPlacemarker, any ideas?
Given the placemark the Google Earth API provides two methods to access the ExtendedData element.
getBalloonHtml()
getBalloonHtmlUnsafe()
API Reference:
https://developers.google.com/earth/documentation/reference/interface_kml_feature
You can find a working example in the Google Code Playground here:
https://code.google.com/apis/ajax/playground/?exp=earth#extended_data_in_balloons
If you wanted to get the raw KML for extended data then you could fetch the KML representation and parse it as an XML document.
var output = placemark.getKml();
Just to say I posted about just this issue on the support forum for the plug-in: https://code.google.com/p/earth-api-samples/issues/detail?id=16
Here is a method I cobbled together to provide support for getExtendedData. It takes a string of Kml as the argument via 'feature.getKml();` It returns any extended data elements that have values in a key[value] object. It expects the extended data to be in the format:
<Data name="Foo">
<value>bar</value>
</Data>
Tested in XP - FF3.0, IE7, Chrome
function getExtendedData(kmlString) {
var xmlDoc = null;
var keyValue = [];
//Parse the kml
try {
//Internet Explorer
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.loadXML(kmlString);
} catch(e) {
try {
//Firefox, etc.
var parser = new DOMParser();
xmlDoc = parser.parseFromString(kmlString,"text/xml");
}
catch(e) {
//Failed to parse
alert(e.message);
return;
}
}
// Get all the named elements
var data = xmlDoc.getElementsByTagName("Data");
// Iterate through the data elements
for(var i=0; i<data.length; i++) {
if(data[i].getAttribute("name") &&
data[i].getElementsByTagName("value").length > 0) {
// Get the name and value
var name = data[i].getAttribute("name");
var value = data[i].getElementsByTagName("value")[0].firstChild.data;
// Assign them to the keyValue object
keyValue[name] = value;
}
}
return keyValue;
}
Usage
// where 'feature' is the object with the extended data
var data = getExtendedData(feature.getKml());
for (var name in data) {
var value = data[name];
alert(name + '=' + value); // e.g. type=images
}
It is actually possible to access the ExtendedData elements via the DOM APIs, although they're not particularly well-documented anywhere.
I found them while grepping around inside some of the resource (.rcc) files packaged with the Plugin.
Assuming a simple Placemark sample similar to yours:
<Placemark id="testmark">
<!-- other stuff... -->
<ExtendedData>
<Data name="someDataUrl">
<displayName>URL Representing some Data</displayName>
<value>http://example.com/#hello</value>
</Data>
</ExtendedData>
</Placemark>
Then (once it's fetched/parsed/loaded into Earth, you can access it something like:
var mark = ge.getElementById('testmark');
var extDataObj = mark.getExtendedData();
var extDataOut = Array(extDataObj.getDataCount());
for (var i = 0; i < extDataObj.getDataCount(); i++) {
var item = extDataObj.getData(i);
var details = { name: item.getName(),
displayName: item.getDisplayName(),
value: item.getValue()
};
extDataOut[i] = details;
}
console.dir(extDataOut);
Haven't tested it for performance vs the .getKml() and feed to an external parser approach, and the lack of official documentation might mean it's not fully functional or supported, but in all testing so far it seems to do ok. I haven't yet found a way to access any of the more complicated SchemaData type structures, only the simple <data name=''><value>... form.

Read a Xml file in Groovy

I am reading a xml file in groovy something like below, but don't know what is going wrong.
Below is my function
def setEnvironment(Map env,Map params)
{
def records=new XmlSlurper().parse(env.get("ABC_HOME")+"/isp/config/nodemeta.xml")
def jdbcurl=records.'domainservice:DBConnectivity'[0].#dbConnectString
params.put("dn", records.'domainservice:GatewayNodeConfig'[0].#domainName)
params.put("dh", records.'domainservice:GatewayNodeConfig'[0].'address'[0].#host)
params.put("dp", records.'domainservice:GatewayNodeConfig'[0].'address'[0].#httpPort)
params.put("u", records.'domainservice:DBConnectivity'[0].#dbUsername)
if(jdbcurl==null||jdbcurl.size()==0)
{
params.put("tns", records.'domainservice:DBConnectivity'[0].#dbHost)
}
else
{
params.put("tns", jdbcurl.find("(?<=%2F%2F)[\\d\\w_]+"))
}
println params
}
My Output
[pd:admin, u:, tns:, dh:, dn:, dp:, un:admin, x:c1164035531]
My Xml
<?xml version="1.0" encoding="UTF-8"?>
<imx:IMX xmlns:imx="http://com.abc.imx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" serializationSpecVersion="4.0" crcEnabled="0" xmlns:domainservice="http://com.abc.isp.metadata.domainservice/2" versiondomainservice="2.4.1" xmlns:common="http://com.abc.isp.metadata.common/2" versioncommon="2.2.0" xsi:schemaLocation="http://com.abc.imx IMX.xsd http://com.abc.isp.metadata.domainservice/2 com.abc.isp.metadata.domainservice.xsd http://com.abc.isp.metadata.common/2 com.abc.isp.metadata.common.xsd">
<domainservice:GatewayNodeConfig imx:id="U:LtWHxY0ZEeGb2FwhP-xoGw" adminconsolePort="15533" adminconsoleShutdownPort="38207" domainName="D_1035531" nodeName="N_1035531" dbConnectivity="ID_1">
<address imx:id="ID_2" xsi:type="common:NodeAddress" host="absdie" httpPort="1531" port="1532"/>
<portals>
<NodeRef imx:id="ID_3" xsi:type="common:NodeRef" address="ID_2" nodeName="N_1035531"/>
</portals>
</domainservice:GatewayNodeConfig>
<domainservice:DBConnectivity imx:id="ID_1" dbEncryptedPassword="ZmTXZDoYq0TyrU7fSaS9BrAlIuZyS2rw%2FafW1TLWE4g%3D" dbHost="fortuner" dbName="ORCL" dbPort="1521" dbType="ORACLE" dbUsername="zx1649355388"/>
</imx:IMX>
Note: pd & x are already there in the map
XmlSlurper is handling the namespaces for you... Just get rid of the namespace part of the node names (note the more Groovy map management as well)
params.dn = records.GatewayNodeConfig[0].#domainName.text()
params.dh = records.GatewayNodeConfig[0].address[0].#host.text()
params.dp = records.GatewayNodeConfig[0].address[0].#httpPort.text()
params.u = records.DBConnectivity[0].#dbUsername.text()

Resources