While I am able to properly parse this payload if I specify the keys via bracket notation, how could you parse it dynamically?
{
"name": "Demo User",
"birthday": "January 1st",
"hobbies":
{
"morning": "coding",
"afternoon": "kewl kids stuff",
"nighttime": "random"
},
"date": "June 25th"
}
What I mean by that is hobbies might not be there, or another field such as 'favorite foods' might exist, which could be an array, or an object.
This scenario has caused me a lot of frustration over the last couple months, and I'm trying to see if anyone can explain to me how to successfully parse it, dynamically.
I found a recursive 'walk' approach that is no longer erroring, but it's returning the first thing it comes across for every iteration.
var data = require("./demo.json");
//data = JSON.stringify(data);
function walk(obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var val = obj[key];
console.log(val);
walk(val);
}
}
}
walk(data);
Basic on what I found, data is being imported as JSON, and if I stringify it, it becomes a string (obviously).
Default
{ name: 'Demo User',
birthday: 'January 1st',
hobbies:
{ morning: 'coding',
afternoon: 'kewl kids stuff',
nighttime: 'random' },
date: 'June 25th' }
Stringify
{"name":"Demo User","birthday":"January 1st","hobbies":{"morning":"coding","afternoon":"kewl kids stuff","nighttime":"random"},"date":"June 25th"}
Both are similar, but the only difference on output is it spamming D X times (Being the first value, I'm thinking?) or spamming { X times (Being the first value of the string?
I've tried a much more basic approach of
var data = require("./demo.json");
for (var key in data){
console.log(key + ':' + data[key]);
}
Which works fine, but as expected, hobbies is returning [object Object] since I am not iterating through it. I could go through hobbies, but again - I don't know if it will exist.
Welcome any input - Generic question, but a process that has caused me a lot of frustration on different projects over last few months.
UPDATE
My vagueness is causing, rightfully-so, confusion.
Let's say my objective is to turn this JSON Payload into a CSV. I need every key for the headers, and every value to be a row under said header.
My issue is, as I iterate through it, I end up with the highest-level objects correctly converted. Then I end up with an object Object column with no data.
For this exact example, let's say my goal is to convert the JSON into
name, birthday, hobbies/morning, hobbies/afternoon, hobbies/nighttime, data
Demo User, January 1st, coding, kewl kids stuff, random, June 25th
Update # 2
Additional array variant.
I would expect
{
...
"hobbies":
{
"morning": "coding",
"afternoon": "kewl kids stuff",
"nighttime": "random"
},
...
}
To output
hobbies/morning, hobbies/afternoon, hobbies/nighttimes
I would expect
{
...
"hobbies": ["coding", "kewl kids stuff", "coding"]
...
}
To output one column
hobbies with quote-enclosed items
"coding, kewl kids stuff, coding"
You can check the type of each value and decide what you want to do,
var data = require("./demo.json");
walk(obj){
for (var key in data){
if(type(data[key]) === "string"){
console.log(key + ':' + data[key]);
}
else if(Array.isArray(data[key])){
//its an array
}
else if(type(data[key]) === "object"){
//its an object
walk(data[key])
}
}
}
The reason your walk function is spamming you with D or { is because it goes on infinite loop when it encountners an string,
function walk(obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var val = obj[key];
console.log(val);
//here you need to check if it is an array or object, only then you should call walk
//calling walk on string would send it on a infinite loop
if(typeof(val) === "object"){
walk(val);
}
}
}
}
Try using this function ( Snippet at the end of the answer )
/**
*
* #param {object} input
* #returns {Array<string>}
*/
function translateObject(input) {
if (typeof input === "object" && input !== null) {
if (input instanceof Array) {
var result = '"';
for (var index in input) {
if (index) result += ", ";
result += input[index];
}
return [result + '"'];
} else {
var data = "", result = "";
for (var key in input) {
if (key.includes(",")) {
throw new Error("Key cannot have a comma");
}
var val = translateObject(input[key]);
if (val.length === 2) {
var titles = val[0].split(", ");
var textIndex = 0;
var size = 0;
for (var index in titles) {
var title = titles[index];
var titleVal = val[1].substring(textIndex, textIndex + title.length);
if (result) { result += ", "; data += ", "; }
textIndex += title.length + 2;
title = key + "/" + title;
size = Math.max(title.length, titleVal.length);
result += title + " ".repeat(size - title.length);
data += titleVal + " ".repeat(size - titleVal.length);
}
} else if (val.length === 1) {
size = Math.max(val[0].length, key.length);
if (result) { result += ", "; data += ", "; }
result += key + " ".repeat(size - key.length);
data += val[0] + " ".repeat(size - val[0].length);
}
}
return [result, data];
}
}
return [input];
}
Here is a working example:
var object = {
"a": "1",
"b": "2",
"c":
{
"e": "3",
"f": "4",
"g": "5"
},
"d": "6"
};
function translateObject(input) {
if (typeof input === "object" && input !== null) {
if (input instanceof Array) {
var result = '"';
for (var index in input) {
if (index) result += ", ";
result += input[index];
}
return [result + '"'];
} else {
var data = "", result = "";
for (var key in input) {
if (key.includes(",")) {
throw new Error("Key cannot have a comma");
}
var val = translateObject(input[key]);
if (val.length === 2) {
var titles = val[0].split(", ");
var textIndex = 0;
var size = 0;
for (var index in titles) {
var title = titles[index];
var titleVal = val[1].substring(textIndex, textIndex + title.length);
if (result) { result += ", "; data += ", "; }
textIndex += title.length + 2;
title = key + "/" + title;
size = Math.max(title.length, titleVal.length);
result += title + " ".repeat(size - title.length);
data += titleVal + " ".repeat(size - titleVal.length);
}
} else if (val.length === 1) {
size = Math.max(val[0].length, key.length);
if (result) { result += ", "; data += ", "; }
result += key + " ".repeat(size - key.length);
data += val[0] + " ".repeat(size - val[0].length);
}
}
return [result, data];
}
}
return [input];
}
function objectToCsv(object) {
var result = translateObject(object);
return result[0] + "\n" + result[1];
}
var csv = objectToCsv(object);
document.querySelector("#console").innerText = csv;
console.log(csv);
#console {
font-family: Courier New,Courier,Lucida Sans Typewriter,Lucida Typewriter,monospace;
white-space: pre;
}
span {
color: darkgrey;
}
<div id="console"></div>
<span>Names were minified to fit result in one line so that it is easier to read</span>
<span>Use this font family if you want all characters to have the same width</span>
Maybe what you are describing is a case where one or more attributes of the object you are expecting is not present, or has no content (or members, if it is an array), and how to build your code on that basis.
It can't be entirely random, otherwise you wouldn't be talking about csv style output. So I will assume that your object contents are mostly there but occasionally you'll find something missing.
If it were me I would pre-process the objects loaded when you use JSON.parse() or whatever equivalent you use to convert the string to javascript objects. I would use something like jquery's $.extend to merge a perfectly formed object into my data, and array merging where the target is an array attribute. This would give me a consistent data model to code against.
In summary - you have to make the data the way you want it to be in order to be able to work with it without surprises.
Related
I'm not good at javascript I've tried to search on stackoverflow.com but unable to find any help I'm trying to create CSV where I want to add content below relevant column heading/index, where the column heading/index is array key and column content is value, below is code but it is showing data only in two columns:
function downloadCSV(csvStr) {
CSV = ConvertToCSV(csvStr);
var uri = [
[
'application.csv','data:text/csv;charset=utf-8,' + escape(CSV)
]];
downloadAll(uri)
}
function ConvertToCSV(objArray) {
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
let str = "";
for (var i = 0; i < array.length; i++) {
var line = '';
for (var index in array[i]) {
if (line != '') line += ''
line += `${index} ,`
line += `${array[i][index]}`
}
str += line + '\n';
}
return str;
}
And below is the array data I have where I want to use First, Street Address as the column heading
0: {First: 'asdkjf,\n', Street Address: 'lasdkfj ,\n', City:
'alsdf,\n', State: 'Alaska,\n', ZIP / Postal Code: 'asl;dfj,\n', …}
1: {First: 'asdkjf,\n', Street Address: 'lasdkfj ,\n', City: 'alsdf,\n', State: 'Alaska,\n', ZIP / Postal Code: 'asl;dfj,\n', …}
This is how I'm getting the result
Consider the following example. This assumes you are receiving JSON Data format.
var myDataArray = [{
"First": "asdkjf",
"Street Address": "lasdkfj",
"City": "alsdf",
"State": "Alaska",
"ZIP / Postal Code": "asl;dfj"
}, {
"First": "asdkjf",
"Street Address": "lasdkfj",
"City": "alsdf",
"State": "Alaska",
"ZIP / Postal Code": "asl;dfj"
}];
function convertToCSV(jsonData) {
var rows = [];
jsonData.forEach(function(obj, i) {
var keys = Object.keys(obj);
keys.forEach(function(key, j) {
rows.push(['"' + key + '"', '"' + obj[key] + '"'].join(","));
});
rows.push('\r\n');
});
return rows.join("\r\n");
}
console.log(convertToCSV(myDataArray));
You can make use of many of the Array tools. This helps create the comma separation. arr.join(",") will result in a String of each element of the array, joined by a ,.
This results in the following:
"First","asdkjf"\r\n
"Street Address","lasdkfj"\r\n
"City","alsdf"\r\n
"State","Alaska"\r\n
"ZIP / Postal Code","asl;dfj"\r\n
\r\n
"First","asdkjf"\r\n
"Street Address","lasdkfj"\r\n
"City","alsdf"\r\n
"State","Alaska"\r\b
"ZIP / Postal Code","asl;dfj"\r\n
\r\n
CSV Format is generally:
Cell1,Cell2,Cell3\r\n
Cell4,Cell5,Cell6\r\n
Where each line is terminated with a Carriage Return (\r) and New Line (\n) character at the End of Line. Some Operating systems do not use the Carriage Return. If this is a Windows format, you will want to use both. More complex CSV content may need to quoted, enclosed within double-quote characters.
See More:
https://en.wikipedia.org/wiki/Comma-separated_values
https://datatracker.ietf.org/doc/html/rfc4180#page-1
I made some changes to the answer given by #Twisty because it was again creating only two columns where column one was heading (array key) and two was content (array value) what I needed was to add all headings (array keys) to top row and content (array value) to relevant box.
I know it's less efficient because I used three loops but it was the only choice to me so far!
Here is the code below:
function ConvertToCSV(objArray) {
var jsonData = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
var rows = [];
let inc = 1;
jsonData.forEach(function(obj, i) {
var keys = Object.keys(obj);
let keyInc = 1;
if (inc === 1) {
keys.forEach(function(key, j) {
if (keyInc === 1) {
rows.push([',"' + key + '"'].join(","));
} else {
rows.push(['"' + key + '"'].join(","));
}
keyInc++
});
rows.push('\n\n');
}
keys.forEach(function(key, j) {
if (obj[key].includes('"')) {
let doubleQuoteValue = obj[key].replaceAll('"', '');
rows.push(['"' + doubleQuoteValue + '"'].join(","));
console.log('double quote',doubleQuoteValue);
} else {
rows.push(['"' + obj[key] + '"'].join(","));
}
console.log(obj[key]);
keyInc++
});
inc++;
rows.push('\n');
});
return rows;
}
I have a JSON string of the form:
[
{"ID":153,"CircuitID":53,"StartTime":"2014-11-12 12:45:00","EventFormatID":224,"TotalPlaces":8,"BookedPlaces":0,"ProvisionalPlaces":0},
{"ID":161,"CircuitID":53,"StartTime":"2014-11-12 17:15:00","EventFormatID":224,"TotalPlaces":0,"BookedPlaces":0,"ProvisionalPlaces":0},
{"ID":734,"CircuitID":53,"StartTime":"2014-11-12 18:30:00","EventFormatID":231,"TotalPlaces":14,"BookedPlaces":0,"ProvisionalPlaces":0}
]
In place of Event Format ID and Circuit ID I will be returning the names
What I need to do is group the results by Event Format ID and return the results in the following format:
Event 224 : 12:45 (8 places available), 17:15 (0 places available)
Event 231 : 18:30 (14 places available)
I can't seem to figure out how to loop through the data, group it by Event Format ID to present it in the required format!
Thanks
Can you use any additional libraries? I'd use lo-dash which would make this relatively simple:
var grouped = _.groupBy(data, "EventFormatID");
_(grouped).forEach(function (group, key) {
console.log("Event:" + key);
_(group).forEach(function (course) {
console.log(course.StartTime + " (" + course.TotalPlaces + " places available)");
});
});
Obviously the example above logs to the console but it'd be fairly simple to change to build up whatever string or object you need.
This is easier with lodash/underscore, or even with ES5 array and object methods, but since you asked about pure JS:
var data = {}, results = [], i, j, id, time, obj, evts; // obj is your object above
for (i=0; i<obj.length; i++) {
id = obj[i].EventFormatID;
time = obj[i].StartTime; // you can simplify to get just the time, not the datetime, if you prefer
data[id] = data[id] || [];
data[id].push({"time":time,"places":obj[i].TotalPlaces});
}
// now you have a proper data structure, just print it out
for (i in data) {
if (data.hasOwnProperty(i)) {
// just show the output properly formatted
evts = [];
for (j=0;i<data[i].length;j++) {
evts.push(data[i][j].time+" ("+data[i][j].places+" places available)");
}
results.push("Event "+i+" : "+evts.join(","));
}
}
ES5 makes this so much easier
var data = {}, results = [], obj; // obj is your object above
obj.forEach(function(val,i) {
data[val.EventFormatID] = data[val.EventFormatID] || [];
data[val.EventFormatID].push({"time":val.StartTime,"places":val.TotalPlaces});
});
// now you have a proper data structure, just print it out
Object.keys(data).forEach(function(key) {
var value = data[key], evts = [];
value.forEach(function(elm) {
evts.push(elm.time+" ("+elm.places+" places available)");
});
results.push("Event "+key+" : "+evts.join(","));
});
And lodash is even easier.
Please take a look that:
http://jsfiddle.net/m260n5ud/
html
<div id="contentDiv"></div>
js
function tidyUp(jsonArray) {
var myObject = {};
for (i = 0; i < jsonArray.length; i++) {
var key = jsonArray[i]['EventFormatID'];
var time = jsonArray[i]['StartTime'].replace(' ', ':').split(/[- :]/);
time = time[3] + ":" + time[4];
var totalPlace = jsonArray[i]['TotalPlaces'];
if (myObject[key] == null) {
myObject[key] = "Event : " + key + " : " + time + " ( " + totalPlace + " places available)";
} else {
myObject[key] += ", " + time + " ( " + totalPlace + " places available)";
}
}
console.log(myObject);
for (var k in myObject) {
document.getElementById('contentDiv').innerHTML += myObject[k] + "<br/>";
}
}
Let's say I get an API response that returns a tree-like structure like this:
"gw43g: (-95.147, 38.5818); " +
"jp987h: (" +
"bvp7: (-97.450, 30.150); " +
"7g8oi: (" +
"34ilht: (-82.192997, 29.39719); " +
"34hb1: (-122.25, 37.47)); " +
"b238: (-71.0349, 42.2129)); " +
"ao8yh: (-90.147, 42.5818);"
Its a string.
Using JavaScript, I need to turn it into an array like this..
["(-95.147, 38.5818)",
"(bvp7: (-97.450, 30.150); 7g8oi: (...)...)",
"(-90.147, 42.5818)"]
..such that the innards of each pair of parenthesizes is an array item, no matter many nested parenthesizes are contained in the outermost pair.
I'm not having any luck, so I'm asking your your guy's help. Here's what I've tried.
function getCoords(str) {
return str.split(';').map(function(s) {
return s.substring(s.indexOf('(')+1, s.lastIndexOf(')'));
});
}
..but that's way wrong. Feel free to call on any functional toolkits (underscore.js, etc). And no, this is not a homework assignment, it's for a book I'm writing. Thanks for any help!
You could use Array.prototype.reduce() like this:
var str = "gw43g: (-95.147, 38.5818); " +
"jp987h: (" +
"bvp7: (-97.450, 30.150); " +
"7g8oi: (" +
"34ilht: (-82.192997, 29.39719); " +
"34hb1: (-122.25, 37.47)); " +
"b238: (-71.0349, 42.2129)); " +
"ao8yh: (-90.147, 42.5818);"
var cnt = 0; // keep count of opened brackets
var result = Array.prototype.reduce.call(str, function(prev, curr) {
if (curr === '(' && cnt++ === 0) prev.push('');
if (cnt > 0) prev[prev.length-1] += curr;
if (curr === ')') cnt--;
return prev;
}, []);
console.log(result);
JSFiddle mirror
You should probably use a parser for this, but here's a quick and dirty solution that's kind of like a mini-parser:
var src = "gw43g: (-95.147, 38.5818) .... ";
var re = /(\w+:\s*\()|(\);?)|((?:(?!\w+:\s*\(|\);?).)+)/g;
var output = [];
var match;
var stackCount = 0;
while ((match = re.exec(src)))
{
if (match[1]) {
if (stackCount == 0) output.push('');
stackCount++;
}
else if (match[2]) {
stackCount--;
}
output[output.length-1] += match[0];
}
console.log(output);
jsFiddle
The regular expression splits tokens into three categories, a stack opener, a stack closer, or neutral. If it finds a stack starter and there is nothing on the stack it adds a new array item, if it finds a closer it brings the stack down by one. Until the stack is at zero, it will keep appending to the current array item.
I couldn't help it, so I went ahead and just finished the simple parser, so that the string is output as an tree where the root properties are the keys (gw43g), and each either has an X, Y value, or it is a branch on the tree.
function parseBody(str) {
// rey: http://rey.gimenez.biz/s/fxd02f
var re = /\s+|(\w+)\s*:\s*\(|(\);?)|(([\-+]?\s*(?:\d*\.\d*|\d+))\s*,\s*([\-+]?\s*(?:\d*\.\d*|\d+)))/g;
var output = [];
var match;
var newObj;
var root = { children: { } }
var branch = root;
while ((match = re.exec(str)))
{
// key and open
if (match[1]) {
newObj = { parent: branch, children: { } };
branch.children[match[1]] = newObj;
// new stack
branch = newObj;
}
//
else if (match[2]) {
// move up stack
branch = branch.parent;
}
else if (match[3]) {
branch.X = parseFloat(match[4]);
branch.Y = parseFloat(match[5]);
}
}
return root;
}
jsFiddle
Regular Expression
I am trying to make a very basic "secret santa" generator as one of my first Javascript projects. I have searched for hours for a solution to this problem but so far nothing has worked that I have found.
I have an array of names which need paired to each other. I successfully have them pairing to each other, but right now someone can be drawn twice. I am pushing the randomly chosen names to another array but I can't find a way to check the randomly chosen names against the ones already chosen.
var names = ["Sean","Kyle","Emily","Nick","Cotter","Brian","Jeremy","Kimmy","Pat","Johnny"];
var used = [];
var picks = [];
if (names.length % 2 != 0) {
alert("You must have an even number of names. You currently have " + names.length + " names.");
}
for( var i = 0; i < names.length; i++){
var random = Math.floor(Math.random()*names.length)
if(names[random] == names[i]) {
names[random] = names[random++];
picks.push(names[i] + " gets " + names[random]);
used.push(names[random]);
} else {
picks.push(names[i] + " gets " + names[random]);
used.push(names[random]);
}
}
console.log("picked array: ")
for(var k=0; k<picks.length; k++) {
console.log(picks[k]);
}
console.log("used array: " + used);
Thank you in advance for any help.
Create two arrays with the names, shuffle them, and make sure you don't pick the same name from both arrays :
var names = ["Sean","Kyle","Emily","Nick","Cotter","Brian","Jeremy","Kimmy","Pat","Johnny"];
if (names.length % 2 != 0) {
alert("You must have an even number of names. You currently have " + names.length + " names.");
} else {
var arr1 = names.slice(), // copy array
arr2 = names.slice(); // copy array again
arr1.sort(function() { return 0.5 - Math.random();}); // shuffle arrays
arr2.sort(function() { return 0.5 - Math.random();});
while (arr1.length) {
var name1 = arr1.pop(), // get the last value of arr1
name2 = arr2[0] == name1 ? arr2.pop() : arr2.shift();
// ^^ if the first value is the same as name1,
// get the last value, otherwise get the first
console.log(name1 + ' gets ' + name2);
}
}
FIDDLE
I would suggest a different approach. Shuffle, split, and zip, no mutation:
var splitAt = function(i, xs) {
var a = xs.slice(0, i);
var b = xs.slice(i, xs.length);
return [a, b];
};
var shuffle = function(xs) {
return xs.slice(0).sort(function() {
return .5 - Math.random();
});
};
var zip = function(xs) {
return xs[0].map(function(_,i) {
return xs.map(function(x) {
return x[i];
});
});
}
// Obviously assumes even array
var result = zip(splitAt(names.length/2, shuffle(names)));
//^
// [
// [ 'Nick', 'Kimmy' ],
// [ 'Sean', 'Johnny' ],
// [ 'Kyle', 'Brian' ],
// [ 'Cotter', 'Pat' ],
// [ 'Emily', 'Jeremy' ]
// ]
There is a multitude of ways you can achieve this.
The fastest to code, but not necessarily the randomest is:
var names = ["Sean","Kyle","Emily","Nick","Cotter","Brian","Jeremy","Kimmy","Pat","Johnny"];
function getPicks(names) {
return names.slice(0).sort(function(){ return Math.random()-0.5 }).map(function(name, index, arr){
return name + " gets " + arr[(index+1)%arr.length];
});
}
getPicks(names);
This is not very random because the shuffling isn't very good and also because you get a single cycle each time. There can be no two cycles A->B->C->A D->E->D.
If you want it to have a random number of cycles of variable length, you can split the names array in several arrays and do the above for each of them, then concatenate the results (see elclanrs).
Finally, the last solution is for each person to pick a person at random and if it's the same one, simply pick again. If the last name remaining in both arrays is the same, simply swap it with another pair.
var names = ["Sean","Kyle","Emily","Nick","Cotter","Brian","Jeremy","Kimmy","Pat","Johnny"];
var a = names.slice(0);
var b = names.slice(0);
var result = [];
while (a.length > 1) {
var i = extractRandomElement(a);
var j = extractRandomElement(b);
while (i===j) {
b.push(j);
j = extractRandomElement(b);
}
result.push({ a:i, b:j });
}
if (a[0] === b[0]) {
result.push({ a:a[0], b:result[0].b });
result[0].b = a[0];
} else {
result.push({ a:a[0], b:b[0] });
}
var pairs = result.map(function(item){ return item.a + ' gets ' + item.b});
function extractRandomElement(array) {
return array.splice(Math.floor(Math.random()*array.length),1)[0];
}
I'm a tad late, but thought I'd throw my answer in here. It essentially does the same thing #adeneo's does, but it uses the same basic code as OP:
var names = ["Sean","Kyle","Emily","Nick","Cotter","Brian","Jeremy","Kimmy","Pat","Johnny"];
pickpool = names.slice(0); // Slice the array at the first element to copy it by value
var used = [];
var picks = [];
if (names.length % 2 != 0) {
alert("You must have an even number of names. You currently have " + names.length + " names.");
}
for( var i = 0; i < names.length; i++){
var random = Math.floor(Math.random()*pickpool.length)
if(names[random] == names[i]) {
// names[random] = names[random++];
picks.push(names[i] + " gets " + pickpool[random++]);
pickpool.splice(random++,1);
} else {
picks.push(names[i] + " gets " + pickpool[random]);
pickpool.splice(random,1);
}
}
console.log("picked array: ");
for(var k=0; k<picks.length; k++) {
console.log(picks[k]);
}
http://jsfiddle.net/SNJpC/
If you don't need to keep the original array you can remove the names as they get selected and each time you pick a name check that it isn't an empty string before pushing it to the next array.
Another consideration...
If you are trying to make a 'Secret Santa' generator, by using random method you can get the same pair next year, and next...
This is another solution where you get all the possible pairs (without repeating a name itself or a pair) for multiple years.
var names = ["Sean", "Kyle", "Emily", "Nick", "Cotter", "Brian", "Jeremy", "Kimmy", "Pat", "Johnny"];
if (names.length % 2 != 0) {
alert("You must have an even number of names. You currently have " + names.length + " names.");
} else {
const arr1 = names.slice()
let arr2 = names.slice();
let countDown = number => {
if (number === 1) {
return;
}
const last = arr2.pop([number - 1]);
arr2.unshift(last);
let pairs = [];
arr1.map(item => {
const index = arr1.indexOf(item);
pairs.push(`${arr1[index]} gets ${arr2[index]}`)
})
console.log(pairs)
return countDown(number - 1);
}
countDown(names.length)
}
I'm trying to find a way to "pretty print" a JavaScript data structure in a human-readable form for debugging.
I have a rather big and complicated data structure being stored in JS and I need to write some code to manipulate it. In order to work out what I'm doing and where I'm going wrong, what I really need is to be able to see the data structure in its entirety, and update it whenever I make changes through the UI.
All of this stuff I can handle myself, apart from finding a nice way to dump a JavaScript data structure to a human-readable string. JSON would do, but it really needs to be nicely formatted and indented. I'd usually use Firebug's excellent DOM dumping stuff for this, but I really need to be able to see the entire structure at once, which doesn't seem to be possible in Firebug.
Use Crockford's JSON.stringify like this:
var myArray = ['e', {pluribus: 'unum'}];
var text = JSON.stringify(myArray, null, '\t'); //you can specify a number instead of '\t' and that many spaces will be used for indentation...
Variable text would look like this:
[
"e",
{
"pluribus": "unum"
}
]
By the way, this requires nothing more than that JS file - it will work with any library, etc.
I wrote a function to dump a JS object in a readable form, although the output isn't indented, but it shouldn't be too hard to add that: I made this function from one I made for Lua (which is much more complex) which handled this indentation issue.
Here is the "simple" version:
function DumpObject(obj)
{
var od = new Object;
var result = "";
var len = 0;
for (var property in obj)
{
var value = obj[property];
if (typeof value == 'string')
value = "'" + value + "'";
else if (typeof value == 'object')
{
if (value instanceof Array)
{
value = "[ " + value + " ]";
}
else
{
var ood = DumpObject(value);
value = "{ " + ood.dump + " }";
}
}
result += "'" + property + "' : " + value + ", ";
len++;
}
od.dump = result.replace(/, $/, "");
od.len = len;
return od;
}
I will look at improving it a bit.
Note 1: To use it, do od = DumpObject(something) and use od.dump. Convoluted because I wanted the len value too (number of items) for another purpose. It is trivial to make the function return only the string.
Note 2: it doesn't handle loops in references.
EDIT
I made the indented version.
function DumpObjectIndented(obj, indent)
{
var result = "";
if (indent == null) indent = "";
for (var property in obj)
{
var value = obj[property];
if (typeof value == 'string')
value = "'" + value + "'";
else if (typeof value == 'object')
{
if (value instanceof Array)
{
// Just let JS convert the Array to a string!
value = "[ " + value + " ]";
}
else
{
// Recursive dump
// (replace " " by "\t" or something else if you prefer)
var od = DumpObjectIndented(value, indent + " ");
// If you like { on the same line as the key
//value = "{\n" + od + "\n" + indent + "}";
// If you prefer { and } to be aligned
value = "\n" + indent + "{\n" + od + "\n" + indent + "}";
}
}
result += indent + "'" + property + "' : " + value + ",\n";
}
return result.replace(/,\n$/, "");
}
Choose your indentation on the line with the recursive call, and you brace style by switching the commented line after this one.
... I see you whipped up your own version, which is good. Visitors will have a choice.
You can use the following
<pre id="dump"></pre>
<script>
var dump = JSON.stringify(sampleJsonObject, null, 4);
$('#dump').html(dump)
</script>
In Firebug, if you just console.debug ("%o", my_object) you can click on it in the console and enter an interactive object explorer. It shows the entire object, and lets you expand nested objects.
For Node.js, use:
util.inspect(object, [options]);
API Documentation
For those looking for an awesome way to see your object, check prettyPrint.js
Creates a table with configurable view options to be printed somewhere on your doc. Better to look than in the console.
var tbl = prettyPrint( myObject, { /* options such as maxDepth, etc. */ });
document.body.appendChild(tbl);
I'm programming in Rhino and I wasn't satisfied with any of the answers that were posted here. So I've written my own pretty printer:
function pp(object, depth, embedded) {
typeof(depth) == "number" || (depth = 0)
typeof(embedded) == "boolean" || (embedded = false)
var newline = false
var spacer = function(depth) { var spaces = ""; for (var i=0;i<depth;i++) { spaces += " "}; return spaces }
var pretty = ""
if ( typeof(object) == "undefined" ) { pretty += "undefined" }
else if ( typeof(object) == "boolean" ||
typeof(object) == "number" ) { pretty += object.toString() }
else if ( typeof(object) == "string" ) { pretty += "\"" + object + "\"" }
else if ( object == null) { pretty += "null" }
else if ( object instanceof(Array) ) {
if ( object.length > 0 ) {
if (embedded) { newline = true }
var content = ""
for each (var item in object) { content += pp(item, depth+1) + ",\n" + spacer(depth+1) }
content = content.replace(/,\n\s*$/, "").replace(/^\s*/,"")
pretty += "[ " + content + "\n" + spacer(depth) + "]"
} else { pretty += "[]" }
}
else if (typeof(object) == "object") {
if ( Object.keys(object).length > 0 ){
if (embedded) { newline = true }
var content = ""
for (var key in object) {
content += spacer(depth + 1) + key.toString() + ": " + pp(object[key], depth+2, true) + ",\n"
}
content = content.replace(/,\n\s*$/, "").replace(/^\s*/,"")
pretty += "{ " + content + "\n" + spacer(depth) + "}"
} else { pretty += "{}"}
}
else { pretty += object.toString() }
return ((newline ? "\n" + spacer(depth) : "") + pretty)
}
The output looks like this:
js> pp({foo:"bar", baz: 1})
{ foo: "bar",
baz: 1
}
js> var taco
js> pp({foo:"bar", baz: [1,"taco",{"blarg": "moo", "mine": "craft"}, null, taco, {}], bleep: {a:null, b:taco, c: []}})
{ foo: "bar",
baz:
[ 1,
"taco",
{ blarg: "moo",
mine: "craft"
},
null,
undefined,
{}
],
bleep:
{ a: null,
b: undefined,
c: []
}
}
I've also posted it as a Gist here for whatever future changes may be required.
jsDump
jsDump.parse([
window,
document,
{ a : 5, '1' : 'foo' },
/^[ab]+$/g,
new RegExp('x(.*?)z','ig'),
alert,
function fn( x, y, z ){
return x + y;
},
true,
undefined,
null,
new Date(),
document.body,
document.getElementById('links')
])
becomes
[
[Window],
[Document],
{
"1": "foo",
"a": 5
},
/^[ab]+$/g,
/x(.*?)z/gi,
function alert( a ){
[code]
},
function fn( a, b, c ){
[code]
},
true,
undefined,
null,
"Fri Feb 19 2010 00:49:45 GMT+0300 (MSK)",
<body id="body" class="node"></body>,
<div id="links">
]
QUnit (Unit-testing framework used by jQuery) using slightly patched version of jsDump.
JSON.stringify() is not best choice on some cases.
JSON.stringify({f:function(){}}) // "{}"
JSON.stringify(document.body) // TypeError: Converting circular structure to JSON
Taking PhiLho's lead (thanks very much :)), I ended up writing my own as I couldn't quite get his to do what I wanted. It's pretty rough and ready, but it does the job I need. Thank you all for the excellent suggestions.
It's not brilliant code, I know, but for what it's worth, here it is. Someone might find it useful:
// Usage: dump(object)
function dump(object, pad){
var indent = '\t'
if (!pad) pad = ''
var out = ''
if (object.constructor == Array){
out += '[\n'
for (var i=0; i<object.length; i++){
out += pad + indent + dump(object[i], pad + indent) + '\n'
}
out += pad + ']'
}else if (object.constructor == Object){
out += '{\n'
for (var i in object){
out += pad + indent + i + ': ' + dump(object[i], pad + indent) + '\n'
}
out += pad + '}'
}else{
out += object
}
return out
}
For anyone checking this question out in 2021 or post-2021
Check out this Other StackOverflow Answer by hassan
TLDR:
JSON.stringify(data,null,2)
here the third parameter is the tab/spaces
This is really just a comment on Jason Bunting's "Use Crockford's JSON.stringify", but I wasn't able to add a comment to that answer.
As noted in the comments, JSON.stringify doesn't play well with the Prototype (www.prototypejs.org) library. However, it is fairly easy to make them play well together by temporarily removing the Array.prototype.toJSON method that prototype adds, run Crockford's stringify(), then put it back like this:
var temp = Array.prototype.toJSON;
delete Array.prototype.toJSON;
$('result').value += JSON.stringify(profile_base, null, 2);
Array.prototype.toJSON = temp;
I thought J. Buntings response on using JSON.stringify was good as well. A an aside, you can use JSON.stringify via YUIs JSON object if you happen to be using YUI. In my case I needed to dump to HTML so it was easier to just tweak/cut/paste PhiLho response.
function dumpObject(obj, indent)
{
var CR = "<br />", SPC = " ", result = "";
if (indent == null) indent = "";
for (var property in obj)
{
var value = obj[property];
if (typeof value == 'string')
{
value = "'" + value + "'";
}
else if (typeof value == 'object')
{
if (value instanceof Array)
{
// Just let JS convert the Array to a string!
value = "[ " + value + " ]";
}
else
{
var od = dumpObject(value, indent + SPC);
value = CR + indent + "{" + CR + od + CR + indent + "}";
}
}
result += indent + "'" + property + "' : " + value + "," + CR;
}
return result;
}
Lots of people writing code in this thread, with many comments about various gotchas. I liked this solution because it seemed complete and was a single file with no dependencies.
browser
nodejs
It worked "out of the box" and has both node and browser versions (presumably just different wrappers but I didn't dig to confirm).
The library also supports pretty printing XML, SQL and CSS, but I haven't tried those features.
A simple one for printing the elements as strings:
var s = "";
var len = array.length;
var lenMinus1 = len - 1
for (var i = 0; i < len; i++) {
s += array[i];
if(i < lenMinus1) {
s += ", ";
}
}
alert(s);
My NeatJSON library has both Ruby and JavaScript versions. It is freely available under a (permissive) MIT License. You can view an online demo/converter at:
http://phrogz.net/JS/neatjson/neatjson.html
Some features (all optional):
Wrap to a specific width; if an object or array can fit on the line, it is kept on one line.
Align the colons for all keys in an object.
Sort the keys to an object alphabetically.
Format floating point numbers to a specific number of decimals.
When wrapping, use a 'short' version that puts the open/close brackets for arrays and objects on the same line as the first/last value.
Control the whitespace for arrays and objects in a granular manner (inside brackets, before/after colons and commas).
Works in the web browser and as a Node.js module.
flexjson includes a prettyPrint() function that might give you what you want.