At first glance, I thought using xml data in javascript would be as simple as finding an xml-to-json library and turning my xml into a javascript object tree.
Now, however, I'm realizing that it's possible to create structures in xml that don't map directly to json.
Specifically, this:
<parentNode>
<fooNode>data1</fooNode>
<barNode>data2</barNode>
<fooNode>data3</fooNode>
</parentNode>
The xml-to-json tools I've found convert the previous to something like this:
{
parentnode:{
foonode:[
'data1',
'data3'
],
barnode:'data2'
}
}
in which, the order of the child nodes has been changed. I need to preserve the order of my child nodes. Anyone have any solution that's more elegant than
a) abandoning the idea of automatic conversion and just designing my own javascript object structure and writing code to handle this specific xml schema
or
b) abandoning the idea of any conversion at all, and leaving my xml data as an xml document which I'll then traverse.
There are established mappings from XML to JSON with limitations (see Converting Between XML and JSON) and mappings from JSON to XML (see JSONx as defined here and conversion rules by IBM). A mapping from XML to JSON that preserves order, however, has not been defined yet. To fully capture all aspects of XML, one should express the XML Infoset in JSON. if you only care about XML elements (no processing instructions, etc.), I'd choose this structure:
[
"parentNode",
{ } /* attributes */
[
[ "fooNode", { }, [ "data1" ] ]
[ "fooNode", { }, [ "data2" ] ]
[ "fooNode", { }, [ "data3" ] ]
]
]
I implemented the same mapping as mapping between XML and Perl data structures that are just like JSON with XML::Struct. The structure further corresponds to the abstract data model of MicroXML, a simplified subset of XML.
If you need the same element name often and you care about ordering it might be better to stay with XML. What benefits do you expect from using JSON?
Why not try:
{ parentNode: [
["fooNode", "data1"],
["barNode", "data2"],
["fooNode", "data3"] ]
}
I think it would more or less solve the problem.
And yes, I think you should abandon automatic conversion if it's not flexible enough; instead you might look for an API that makes such mappings trivial.
I devised this, recently:
(just a thought experiment)
var someTinyInfosetSample = {
"doctype": "html",
"$": [
{ "": "html" },
[ { "": "head" },
[ { "": "title" }, "Document title" ]
],
[ { "": "body" },
[ { "": "h1" }, "Header 1" ],
[ { "": "p", "class": "content" },
"Paragraph... (line 1)", [ { "": "br" } ],
"... continued (line 2)"
]
]
] };
(at https://jsfiddle.net/YSharpLanguage/dzq4fe39)
Quick rationale:
XML elements are the only node type (besides the document root) which accepts mixed content (text nodes and/or other elements, comments, PIs, and defines an order of its child nodes; hence the use of JSON arrays (child indices being then 1-based, instead of 0-based, because of the reserved index 0 to carry the node type (element) info; but one can note that XPath nodesets also use a 1-based index, btw);
XML attribute name/value maps don't need any ordering of the keys (attribute names) wrt. their owner element, only uniqueness of those at that element node; hence the use of a JSON object at index 0 of the container array (corresp. to the owner element);
and finally, after all, while "" is a perfectly valid JSON key in object values, it's also the case that neither XML elements or attributes can have an empty name anyway... hence the use of "" as a special, conventional key, to provide the element name.
And here's what it takes to turn it into HTML using my small "JSLT" (at https://jsfiddle.net/YSharpLanguage/c7usrpsL/10):
var tinyInfosetJSLT = { $: [
[ [ function/*Root*/(node) { return node.$; } ],
function(root) { return Per(this).map(root.$); }
],
[ [ function/*Element*/(node) { return { }.toString.call(node) === "[object Array]"; } ],
function(element) {
var children = (element.length > 1 ? element.slice(1) : null),
startTag = element[0],
nodeName = startTag[""],
self = this;
return children ?
Per("\r\n<{stag}>{content}</{etag}>\r\n").map
({
stag: Per(this).map(startTag),
etag: nodeName,
content: Per(children).map(function(child) { return Per(self).map(child); }).join("")
})
:
Per("<{stag}/>").map({ stag: Per(this).map(startTag) });
}
],
[ [ function/*StartTag*/(node) { return node[""]; } ],
function(startTag) {
var tag = [ startTag[""] ];
for (var attribute in startTag) {
if (attribute !== "") {
tag.push
(
Per("{name}=\"{value}\"").
map({ name: attribute, value: startTag[attribute].replace('"', """) })
);
}
}
return tag.join(" ");
}
],
[ [ function/*Text*/(node) { return typeof node === "string"; } ],
function(text) {
return text.
replace("\t", "&x09;").
replace("\n", "&x0A;").
replace("\r", "&x0D;");
}
]
] };
(Cf. https://jsfiddle.net/YSharpLanguage/dzq4fe39/1)
where,
Per(tinyInfosetJSLT).map(someTinyInfosetSample)
yields (as a string):
<html>
<head>
<title>Document title</title>
</head>
<body>
<h1>Header 1</h1>
<p class="content">Paragraph... (line 1)<br/>... continued (line 2)</p>
</body>
</html>
(but above the transform could also be easily adapted to use a DOM node factory, and build an actual DOM document, instead of building a string)
'HTH,
Related
I have a CSV file and I want to parse it using PapaParse. How do I do this properly?
I have so far:
Papa.parse(fileInput, {
download: true,
complete: function(results) {
console.log(results.data);
console.log(results.errors);
}
});
However, is there a better way to do this? Is this the proper way to get errors? The documentation didn't emphasize download: true or anything so I was wondering if there are any experts on this subject here.
EDIT: Also, am I suppose to further parse the file with papacsv or do it in react. For instance, if I have multiple arrays in my data file which have a similar name reference. Should I initially somehow parse the file so it groups all those references together and how would I go about doing this?
For instance,
Date, Name , Win/Lose
I want to group all the winners together. How do I do that?
The method you are using of Papa parse, is for remote CSV.
download: true is for downloading the remote file.
By using Papa parse, this is the only way of getting errors, data, meta with parse result object.
//If(header:true)
var data = [
{
"date": "8/12/2018",
"name": "foo",
"win/loose": "win"
},
{
"date": "8/12/2018",
"name": "foo",
"win/loose": "loose"
},
{
"date": "8/12/2018",
"name": "foo1",
"win/loose": "win"
},
];
var winners = data.filter(d => d['win/loose'] == 'win');
console.log(winners);
//If you want to group winners and losers then:
var grouped = data.reduce(function(acc, co) {
var key = co['win/loose'];
if(!acc[key]) {
acc[key] = [];
}
acc[key].push(co);
return acc;
}, {});
console.log(grouped);
This'll give you separate array of winners from extracted data.
I am writing to a json file in casperjs and am trying to add new objects to it.
json file looks like
{ "visited": [
{
"id": "258b5ee8-9538-4480-8109-58afe741dc2f",
"url": "https://................"
},
{
"id": "5304de97-a970-48f2-9d3b-a750bad5416c",
"url": "https://.............."
},
{
"id": "0fc7a072-7e94-46d6-b38c-9c7aedbdaded",
"url": "https://................."
}]}
The code to add to the array is
var data;
if (fs.isFile(FILENAME)) {
data = fs.read(FILENAME);
} else {
data = JSON.stringify({ 'visited': [] });
}
var json = JSON.parse(data);
json.visited.push(visiteddata);
data = JSON.stringify(json, null, '\n');
fs.write(FILENAME, data, "a");
This is starting off by adding an new { "visited" : [ ] } array with first couple of objects, below the existing { "visited" : [ ] } array and subsequently the script breaks because the json array is no longer valid json.
Can anybody point me in the right direction. Thank you in advance.
You have a JSON file containing some data.
You:
Read that data
Modify that data
Append the modified version of that data to the original file
This means the file now has the original data and then, immediately after it, a near identical copy with a little bit added.
You don't need the original. You only need the new version.
You need to write to the file instead of appending to it.
Change the 'a' flag to 'w'.
i have a JSON js obejct in
res.metrics.load.1-min
the problem is that this is coming from the server.
i cannot extract anything as it gives illegal number
since res.metrics.load.1-min contains 1-min
Any suggestion i can i parse my JSON. my JSON is an Array
"metrics" : {
"load" : {
"1-min" : [
[
5.87,
1437031875
],
[
5.87,
1437031890
]
]}}
Please help i am using
res.metrics.load.1-min = res.metrics.load.1-min.map(
function (map)
{
return { x: map[1], y: map[0] };
});
to map values to x and y. its throwing an error.
You need to update
res.metrics.load.1-min
to
res.metrics.load["1-min"]
I have a JSON object which comes back like this from a JavaScript API call:
{
"myArray": [
{
"version": 5,
"permissionMask": 1
},
{
"version": 126,
"permissionMask": 1
}
]
}
How can I access the name of the array (i.e myArray) in JavaScript. I need to use the name of the array to determine the flow later on.
Use getOwnPropertyNames to get a list of the properties of the object in array form.
Example:
var myObj = {
"myArray": [
{
"version": 5,
"permissionMask": 1
},
{
"version": 126,
"permissionMask": 1
}
]
},
names = Object.getOwnPropertyNames(myObj);
alert(names[0]); // alerts "myArray"
Note: If the object can have more than one property, like myArray, myInt, and myOtherArray, then you will need to loop over the results of getOwnPropertyNames. You would also need to do type-testing, as in if(names[0] instanceof Array) {...} to check the property type. Based on your example in your question, I have not fleshed all of that out here.
Object.keys(data)[0]
# => "myArray"
A terminology note: This solution assumes you have a JavaScript object. You might have a JSON string, in which case this is the solution:
Object.keys(JSON.parse(data))[0]
# => "myArray"
However, "JSON object", in JavaScript, is just one - the one I used just now, that has JSON.parse and JSON.stringify methods. What you have is not a JSON object except perhaps in a trivial interpretation of the second case, where all values in JavaScript are objects, including strings.
The other answers are good if you have no control over the return format.
However, if you can, I'd recommend changing the return format to put the important values you care about as actual values instead of keys to make it clearer. For example, something like this:
result =
{
"name: "myArray",
"value": [
{
"version": 5,
"permissionMask": 1
},
{
"version": 126,
"permissionMask": 1
}
]
}
Then, it's a lot clearer to reliably access the property you care about: result.name
I am trying to visualize team collaboration data, in a way like this:
Different colors in the chart are different collaboration artifact types.
The data from the source looks like this:
var json = [
{
"teamLabel": "Team 1",
"created_date": "2013-01-09",
"typeLabel": "Email"
"count": "5"
},
{
"teamLabel": "Team 1",
"created_date": "2013-01-10",
"typeLabel": "Email"
"count": "7"
},
/* and of course, a lot more data of this kind */
]
Note that the data is given for single days. So for the above visualization, I need to aggregate the data based on the week of year first. The team name and the artifact type need to be preserved though and are used as grouping attributes. Here's the code:
// "2013-01-09"
var dateFormat = d3.time.format.utc("%Y-%m-%d");
// "2013-02" for the 2nd week of the year
var yearWeek = d3.time.format.utc("%Y-%W");
var data = d3.nest().key(function(d) {
return d.teamLabel;
}).key(function(d) {
var created_date = dateFormat.parse(d.created_date);
return yearWeek(created_date);
})
.key(function(d) {
return d.typeLabel;
}).rollup(function(leaves) {
return d3.sum(leaves, function(d) {
return parseInt(d.count); // parse the integer
});
}
)
.map(json);
This results in an Object hierarchy based on the nesting keys. I do not see how to create the above chart from this, so I am rather looking for a way to convert data into the following structure:
[
// This list contains an element for each donut
{
"teamLabel": "Team 1",
"createdWeek": "2013-02",
"values": [
// This list contains one element for each type we found
{
"typeLabel": "Email",
"count": 12
},
{
...
}
]
},
{
...
}
]
This way, I can use createdWeek and teamLabel for the positioning on x- and y-Axis respectively, and the information under values can be passed to d3.layout.pie().
Is there a clean way to do this data transformation? If you need any clarification or further details, please let me know.
That's how you do it:
var flat = data.entries().map(function(d){
return d.value.entries().map(function(e){
return {
"teamLabel": d.key,
"createdWeek": e.key,
"values": e.value.entries().map(function(f){
return {"typeLabel": f.key, "count": f.value}
})
}
})
}).reduce(function(d1,d2){ return d1.concat(d2) },[]);
Note that I'm using d3.map instead of the standard javascript object in order to use the map.entries() helper function. I imagine that's what you tried judging by the fact that you're using:
.map(json); // whereas I used .map(json, d3.map)
instead of
.entries(json);
jsFiddle link here:
http://jsfiddle.net/RFontana/KhX2n/