Parsing some JSON - javascript

I have a complex JSON file that needs parsing and my loop skills (or more precisely, the lackthereof), are really failing me.
I have the following xml file, and I am trying to get all elements on one row. In my perfect world (in no particular order)...
sku #, length, width, image, description, attribute value 1, attribute value 2, attribute value 3, etc.
The JSON file is as follows:
var json = {
"product":[
{
"shipdata":{
"_length":"2in",
"_width":"2in",
},
"sku":"90245",
"brand":"Brandy",
"image":"shirt.jpg",
"description":"description",
"attributes":{
"attribute":[
{
"_name":"Color",
"_value":"Black",
},
{
"_name":"Gender",
"_value":"Mens",
},
{
"_name":"Size",
"_value":"L",
},...
So, my intended result is:
90245, Brandy, Black, Men's, L, shirt.jpg, 2in, 2in
But when I loop like the following, I only get the first result for "name". Admittedly, I'm a newb, but if anyone can push me in the right direction or show a proof of concept, it would be so so appreciated. Thanks in advance / feel horrible to even ask such a low level question.
for(var l = 0; l < json.product[i].attributes.attribute.length; l++) {
var xxx = (json.product[i].attributes.attribute[l]['_name']);
}
$('body').append(xxx);

if you don't mind using lodash, this should help you:
var res=[];
_.each(json.product, function(p) {
res.push(p.brand);
res.push(p.sku);
_.each(p.attributes.attribute, function(at) {
res.push(at._value);
});
});
console.log(res.join(','));
//Brandy,90245,Black,Mens,L
working fiddle

EDIT: My solution is obviously not as good as scottjustin5000 's. I'm trying to explain the detailed steps on analyzing this problem.
You want to output a string from the JSON data. So we should break the parts of the string and process one by one.
90245, Brandy, Black, Men's, L, shirt.jpg, 2in, 2in
"sku", "brand", attribute, attribute, attribute, "image", "_length", "_width"
Let's start.
function parseJSONToLine(product) {
var line = "";
line = line + product["sku"] + ", ";
line = line + product["brand"] + ", ";
line += getAllAttributes(product);
line = line + product["image"] + ", ";
line = line + product["shipdata"]["_length"] + ", ";
line = line + product["shipdata"]["_width"];
return line;
}
products = json["product"];
for (var i = 0; i < products.length; i++) {
console.log(parseJSONToLine(products[i]));
}
This part is just assembling the line your want part by part. For the attributes, we need another loop:
function getAllAttributes(product) {
var attrStr = "";
var attrsDict = {};
var attrsOrder = ["Color", "Gender", "Size"];
var attrList = product["attributes"]["attribute"];
// loop through every attribute and put it in dictionary
for (var i = 0; i < attrList.length; i++) {
attrsDict[attrList[i]["_name"]] = attrList[i]["_value"];
}
for (var i = 0; i < attrsOrder.length; i++) {
attrStr = attrStr + attrsDict[attrsOrder[i]] + ", ";
}
return attrStr;
}
The last part is to put the line produced into your HTML. Just the $(body') line with:
$('body').append('<p>' + line + '</p>');
That's it. The point to solve this problem is to know what the line is consisted of. Then try to get the values in the JSON object one by one. When meeting something seems to be complicated, just try to write out the code and modify according to the output. console.log() is very helpful on this.
The reason of why your code doesn't work is, your JSON data contains not only arrays but also objects. You have to take them apart.
If you need further explanation on the snippet, comment me.
JSFiddle: https://jsfiddle.net/aresowj/g9wuLg28/

According to your JSON structure and the output you want, I'll suggest to do the following:
var output = Array(json.product.length); // will be an array of string
for(var i = 0; i < json.product.length; i++) {// loop on each product
output[i] = json.product[i].sku +', '+json.product[i].brand; // according to your question, seems that you want these 2 things first
for(var j = 0; j < json.product[i].attributes. attribute.length; j++){ // then we loops on the attributes
output[i] += ', ' +json.product[i].attributes. attribute[j]._name;
}
output[i] += ', ' +json.product[i].shipdata._length + ', ' + json.product[i].shipdata._width; // last we append to the string the with and height data
}
$('body').append(output)
var json = {
"product":[
{
"shipdata":{
"_length":"2in",
"_width":"2in",
},
"sku":"90245",
"brand":"Brandy",
"image":"shirt.jpg",
"description":"description",
"attributes":{
"attribute":[
{
"_name":"Color",
"_value":"Black",
},
{
"_name":"Gender",
"_value":"Mens",
},
{
"_name":"Size",
"_value":"L",
}
]
}
}
]
};
var output = Array(json.product.length); // will be an array of string
for(var i = 0; i < json.product.length; i++) {// loop on each product
output[i] = json.product[i].sku +', '+json.product[i].brand; // according to your question, seems that you want these 2 things first
for(var j = 0; j < json.product[i].attributes. attribute.length; j++){ // then we loops on the attributes
output[i] += ', ' +json.product[i].attributes. attribute[j]._name;
}
output[i] += ', ' +json.product[i].shipdata._length + ', ' + json.product[i].shipdata._width; // last we append to the string the with and height data
}
$('body').append(output)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

A solution using Array.map:
var res = json.product.map(function (p) {
return [p.sku, p.brand].concat(p.attributes.attribute.map(function (at) {
return at._value;
}))
});
res.forEach(function (r) { console.log(r.join(', ')) });
https://jsfiddle.net/xve4agp6/1/

Related

How to format the element inside an array?

I have three arrays for example:
var name = ["wheel", "rectangle", "moon"];
var type = ["car", "shape", "sky"];
var all = [];
var temp = " ";
for (var i = 0; i < name.length; i++) {
temp = name[i] + " " + type[i];
all.push(temp);
}
for (var i = 0; i < name.length; i++) {
// I call here function to display all element of array `all`
}
The output is:
wheel car
rectangle shape
moon sky
But the format of output is not nice. I want to shift the element of array type before add them to array all, so I want the output to be like:
wheel car
rectangle shape
moon sky
My question is: how can I shift elements of the array to add them to another array and store them in a way that allows to me to display the elements like form above ?
But the form of output not nice
If you simply want to format the output in a better way, then try console.table
var name1 = [ "wheel","rectangle","moon" ];
var type = [ "car" , "shape", "sky"];
var all=[];
for (var i = 0; i< name1.length; i++)
{
all.push({ name : name1[i], type: type[i] });
}
console.table(all);
Try this fiddle to see the actual output since stack-snippet alters the behaviour of console api
You should calculate which is the longest string in the first array so to know in advance how many spaces you need to append to correctly pad the string
var n = ["wheel", "rectangle", "moon"];
var t = ["car", "shape", "sky"];
var all = [];
/* sorting the values of the first array by length desc,
* then get the length of the first element
*/
var padding = n.sort(function(a, b) {
return a.length <= b.length;
})[0].length + 1;
n.forEach(function(el, i) {
all.push(el + " ".repeat(padding - el.length) + t[i]);
});
Output
"rectangle car"
"wheel shape"
"moon sky"
codepen demo
First loop over the array and find the max length. Then loop again and add spaces.
<script >
var name=["wheel","rectangle","moon"];
var type=["car","shape","sky"];
var all=[];
var i=0;
var maxLength=0;
string temp=" ";
String.prototype.padLeft= function(len, c){
var r = '';
while(r.length < len) r += c;
return s+r;
}
for (i = 0; i< name.length; i++)
{
maxLength = Math.max(maxLength, name[i].length+type[i].length+1;
}
for (i = 0; i< name.length; i++)
{
temp=name[i]+type[i].padLeft(maxLength-name[i].length-type[i].length);
all.push(temp);
}
</script >
I would do as follows;
var id = ["wheel","rectangle","moon"],
type = ["car","shape","sky"];
id.longestStringLength = Math.max(...id.map(s => s.length));
type.longestStringLength = Math.max(...type.map(s => s.length));
id = id.map((s,_,a) => s + " ".repeat(a.longestStringLength-s.length));
type = type.map((s,_,a) => " ".repeat(a.longestStringLength-s.length) + s);
console.log(id,type);
Use \t instead of space while concatenating to make it aligned.
Why don't you just add tab '\t' and it will give you the desired output. Or you can append fixed number of spaces between the two array items.

Javascript - Continue statement not working?

Im currently learning about break and continue statements. It prints the 1st array, the 2nd array runs the alert like it suppose to, but the third one doesn't run, when i use the continue statement. Maybe im not doing it right? some guidance for a newbie would be nice.
Im using JSBin to run this.
p.s. im learning from the "Begining Javascript" book
Thanks
var n = [233, "john", 432];
var nIndex;
for (nIndex in n) {
if (isNaN(n[nIndex])) {
alert(n[nIndex] + " is not a number");
continue;
}
document.write(n[nIndex] + " ");
}
Continue does not work in :
for(i in array) {}
it works for for(i=0; i<n; i++){}
This is how you iterate over the elements of an array:
var data = [233, "john", 432];
for (var i = 0; i < data.length; ++i) {
if (isNaN(data[i])) {
alert(data[i] + " is not a number");
continue;
}
document.write(data[i] + " ");
}
By the way, you can remove the continue statement and instead use else on the alternate instructions:
var data = [233, "john", 432];
for (var i = 0; i < data.length; ++i) {
if (isNaN(data[i])) {
alert(data[i] + " is not a number");
} else {
document.write(data[i] + " ");
}
}
That's logically equivalent and you may find it easier to read.

Javascript: using a for statement as a variable

I'm fairly new to javascript and something I've been playing with lately is the 'for' statement. I'm questioning one thing, though. I've learned how to make a 'for' statement do things as if it was an output, like this:
for (i = 0; i < 3; i++) {
console.log(i);
}
But what if you want to set a variable for the whole output of the 'for' statement?
var destinationArray = ["town", "areas", "bosses"];
var destinationArraySet = 1;
var i;
for ( i = 0; i < destinationArraySet; i++) {
console.log(destinationArray[i]);
} /*the whole thing should be equal to var destination */
var userDestinationPrompt = ("Where would you like to go? Available places: " +
/* var destination */
+
".").toUpperCase();
To give some more context: I'm making a game that allows further destinations when the destination before is cleared. Once that's achieved, I set destinationArraySet to a higher value, which means that more places would be logged and put after 'Available places'.
Help would be very appreciated! If there's something not clear enough let me know.
The for statement is not an expression, so it doesn't have a return value. Use a variable to collect values in the loop:
var destination = '';
for (var i = 0; i < destinationArraySet; i++) {
destination += destinationArray[i] + ' ';
}
Of course, if you only want to concatenate the values in part of an array, you can use the slice method to get part of it, then the join method:
var destination = destinationArray.slice(0, destinationArraySet).join(' ');
var destination = '';
var destinationArray = ["town", "areas", "bosses"];
var destinationArraySet = 1;
for (var i = 0; i < destinationArraySet; i++) {
destination += destinationArray[i] + '\n';
}
console.log(destination);
Try this -
var destinationArray = ["town", "areas", "bosses"];
var destinationArraySet = 1;
var i;
var availablePlaces = '';
var separator = '';
for ( i = 0; i < destinationArraySet; i++) {
availablePlaces += separator + destinationArray[i];
separator = ', ';
}
var userDestinationPrompt = ("Where would you like to go? Available places: " +
availablePlaces + ".").toUpperCase();
The for statement doesn't have an "output", it's not a function. Thinking for as a function will give you troubles later on. for is simply a statement that continuously execute the block of code inside. It does not "output", or in other words, return any value.
Do this instead:
var destinationArray = ["town", "areas", "bosses"], destinationArraySet = 1;
var userDestinationPrompt = ("Where would you like to go? Available places: " +
destinationArray.slice(0, destinationArraySet).join("\n")
+ ".").toUpperCase();
prompt(userDestinationPrompt);
Demo: http://jsfiddle.net/7c2b9q7m/1/
destinationArray.slice(0, destinationArraySet): Cuts the array to the specified length.
.join("\n"): Join the newly created array by \ns (newline) to micic the default console.log behavior.

Google Apps Script: How to fix this for loop to insert paragraphs of one google document to another?

So I'm arriving at last stops of my Apps journey (there'll be several others =).
The code bellow is a function that aims to do this: iterates through the paragraphs of one google text document and, when it finds in text some sinal, some paragraph (such as "Introduction", "Part 1 - Background" or "Part 2 - Biography") whose content is igual a theses_type, it inserts all theses_type paragraphs into the first document, after that sinal or theses_type keyword.
So, I'm trying to do this with the function "importTheses" (thanks #Serge insas and others for previous help!). But I'm having trouble after the line for( var k = 0; k < thesesParagraphs-1; ++k ). Even when I got through the log the boolean True, I can't get the paragraphs inserted. I also can't get the log of this line: Logger.log("thesesDoc.getText() = " + thesesElement.getText() );. So, any help or hint will be very appreciated.
function importTheses(targetDocId, thesesId, thesesType) { // just a name, I used it to analyse docs
var targetDoc = DocumentApp.openById(targetDocId);
var targetDocParagraphs = targetDoc.getParagraphs();
var targetDocElements = targetDoc.getNumChildren();
var thesesDoc = DocumentApp.openById(thesesId);
var thesesParagraphs = thesesDoc.getParagraphs();
var thesesElements = thesesDoc.getNumChildren();
Logger.log("targetDocId = " + targetDocId);
Logger.log("thesesId = " + thesesId);
Logger.log("thesesType = " + thesesType);
var elTargetDoc=[];
var elTheses=[];
for (var j = 0; j < targetDocElements; ++j ) {
var targetDocElement = targetDoc.getChild(j);
Logger.log("targetDoc.getChild(j) = " + targetDocElement);// to see targetDoc's content
elTargetDoc[j]=targetDocElement.getText();
if(elTargetDoc[j] == thesesType){
Logger.log("elTargetDoc[j]== " + elTargetDoc[j]);
Logger.log("thesesType " + thesesType);
Logger.log("if(elTargetDoc[j]== thesesType)" + (elTargetDoc[j]== thesesType) );
for( var k = 0; k < thesesParagraphs-1; ++k ) {
var thesesElement = thesesDoc.getChild(k);
Logger.log("thesesDoc.getChild(k) " + thesesDoc.getChild(k));
Logger.log("thesesDoc.getText() = " + thesesElement.getText() );
elTheses[k] = thesesElement.getText();
targetDoc.insertParagraph(j, elTheses[k]);
}
}
}
}
for( var k = 0; k < thesesParagraphs-1; ++k ) { .. }
In this line of code, you're intention is to loop using k, over integer values starting at 0. The loop should run at least once, as long as thesesParagraphs is 2 or more... and is a number.
That second condition is your problem. Earlier in the function, you had this:
var thesesParagraphs = thesesDoc.getParagraphs();
... so thesesParagraphs is an Array of Paragraph objects, not a number. You are probably interested in the count of paragraphs:
for( var k = 0; k < thesesParagraphs.length-1; ++k ) { .. }
^^^^^^^
Or perhaps thesesElements was what you intended to use to bound your loop, since it's a number.

Merging multiple columns as the table structure is written

I am trying to group multiple columns on my table by collapsing them into a single column and then use a UL list to separate the categories:
For this, what I am doing is adding a boolean to toggle the start/stop stacking, like this:
cols[0][0]="Name";
cols[0][1]=true; //<--toggler
cols[1][0]="Age";
cols[1][1]=false;
cols[2][0]="[M/F]";
cols[2][1]=false;
cols[3][0]="E-mail";
cols[3][1]=true;//<--toggler
However, using this method I have some problems:
I haven't managed to make two consecutive groups: [A][B+C][D+E][F]
the code is pretty hard to read, also to understand exactly what the toggle does
My code to write the head is the following:
document.writeln("<table><thead><tr>");
tmp = "<th>#";
flag = false;
for(i = 0; i < cols_len; i++){
if(cols[i][1]){ //if stack-toggler
if(flag){
tmp += "</th><th>-";
}
flag = !flag;
}
if(!flag){
tmp += "</th><th>" + cols[i][0];
}
}
if(flag){
tmp += "</th><th>-";
}
document.writeln(tmp + "</th></tr></thead><tbody>");
And then for the body of the table:
for(i = 0; i < 20; i++){ //some number of rows
if(i){
document.writeln("</tr>");
}
document.writeln("<tr><td>" + i + "</td>");
tmp = "";
flag = false;
for(j = 0; j < cols_len; j++){
if(cols[j][1]){ //if stack-toggler
if(flag){
document.writeln("<td><ul>" + (tmp.replace(/<td/g, "<li").replace(/td>/g, "li>")) + "</ul></td>");
tmp = "";
}
flag = !flag;
}
if(flag){
tmp += "<strong>" + cols[j][0] + ":</strong><br>";
}
tmp += "<td>...</td>";
if(!flag){
document.writeln(tmp);
tmp = "";
}
}
if(flag){
document.writeln("<td><ul>" + (tmp.replace(/<td/g, "<li").replace(/td>/g, "li>")) + "</ul></td>");
}
}
document.writeln("</tr></tbody></table>");
»The full code and demo can be found in this jsFiddle.
I feel this is the wrong approach, it sucks in every way and more importantly, I can't have two or more consecutive groups!, after I start stacking columns, whenever I want to stop, the next column must be alone (and not another group).
I have played around with the booleans and it is simply impossible, I can't figure it out, I already reduced the code to the most readable way and tried to rewrite parts of it but I keep getting the same results.
I made a slight change to the data model. Instead of "toggle" the boolean says if the next guy stacks or not (that is if it is true then put the next one below me.)
So the data looks like this:
/*0:caption, 1:stack-toggler*/
cols[0][0]="Name";
cols[0][1]=true; //<--stack next below
cols[1][0]="Age";
cols[1][1]=true; //<--stack next below
cols[2][0]="[M/F]";
cols[2][1]=false;
cols[3][0]="E-mail";
cols[3][1]=false;
cols[4][0]="Website";
cols[4][1]=false;
cols[5][0]="Notes";
cols[5][1]=false;
And then the code can be simple -- like this (a trick I used, you can change the loop variable internal to the loop -- so we only loop on the outer loop when we change columns.)
for(i = 0; i < 20; i++){ //some number of rows
buildHTML("<tr><td>" + i + "</td>");
for(j = 0; j < cols_len; j++){
buildHTML("<td>");
if (cols[j][1]) {
buildHTML("<ul>"+cols[j][0]+"</ul>");
// loop till we are at the penultimate
while (cols[j+1][1]) {
j++;
buildHTML("<ul>"+cols[j][0]+"</ul>");
}
j++;
buildHTML("<ul>"+cols[j][0]+"</ul>");
}
else {
buildHTML(cols[j][0]);
}
buildHTML("</td>");
}
buildHTML("</tr>");
}
buildHTML("</tbody></table>");
I did not bother with the header, you can figure that out I'm sure.
Here is the fiddle:
http://jsfiddle.net/eajQy/3/
Of course with javascript you can get fancy. Since what you really have is an array of arrays for the columns you could represent that like this:
// array of arrays -- one element std, 2 or more a list
newcols = [ ["Name","Age","[M/F]"] , ["E-Mail"] , ["Website"], ["Notes"] ];
Then to make your table you could use map and join like this:
buildHTML("<tr><td>" + i + "</td>");
strArray = $.map(newcols, function (anArray) {
if (anArray.length == 1) {
return "<td>"+anArray[0]+"</td>";
}
else {
return "<td><ul>"+anArray.join("</ul><ul>")+"</ul></td>";
}
});
buildHTML(strArray.join(""));
buildHTML("</td></tr></tbody></table>");
Here is a fiddle:
http://jsfiddle.net/eajQy/4/
Single line solution (because every question should have a single line solution):
http://jsfiddle.net/eajQy/5/

Categories

Resources