How can I convert a JavaScript object into a string?
Example:
var o = {a:1, b:2}
console.log(o)
console.log('Item: ' + o)
Output:
Object { a=1, b=2} // very nice readable output :)
Item: [object Object] // no idea what's inside :(
I would recommend using JSON.stringify, which converts the set of the variables in the object to a JSON string.
var obj = {
name: 'myObj'
};
JSON.stringify(obj);
Most modern browsers support this method natively, but for those that don't, you can include a JS version.
Use javascript String() function
String(yourobject); //returns [object Object]
or stringify()
JSON.stringify(yourobject)
Sure, to convert an object into a string, you either have to use your own method, such as:
function objToString (obj) {
var str = '';
for (var p in obj) {
if (Object.prototype.hasOwnProperty.call(obj, p)) {
str += p + '::' + obj[p] + '\n';
}
}
return str;
}
Actually, the above just shows the general approach; you may wish to use something like http://phpjs.org/functions/var_export:578 or http://phpjs.org/functions/var_dump:604
or, if you are not using methods (functions as properties of your object), you may be able to use the new standard (but not implemented in older browsers, though you can find a utility to help with it for them too), JSON.stringify(). But again, that won't work if the object uses functions or other properties which aren't serializable to JSON.
Update:
A more modern solution would be:
function objToString (obj) {
let str = '';
for (const [p, val] of Object.entries(obj)) {
str += `${p}::${val}\n`;
}
return str;
}
or:
function objToString (obj) {
return Object.entries(obj).reduce((str, [p, val]) => {
return `${str}${p}::${val}\n`;
}, '');
}
Keeping it simple with console, you can just use a comma instead of a +. The + will try to convert the object into a string, whereas the comma will display it separately in the console.
Example:
var o = {a:1, b:2};
console.log(o);
console.log('Item: ' + o);
console.log('Item: ', o); // :)
Output:
Object { a=1, b=2} // useful
Item: [object Object] // not useful
Item: Object {a: 1, b: 2} // Best of both worlds! :)
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Console.log
EDIT Do not use this answer as it works only in some versions of Firefox. No other browsers support it. Use Gary Chambers solution.
toSource() is the function you are looking for which will write it out as JSON.
var object = {};
object.first = "test";
object.second = "test2";
alert(object.toSource());
One option:
console.log('Item: ' + JSON.stringify(o));
Another option (as soktinpk pointed out in the comments), and better for console debugging IMO:
console.log('Item: ', o);
None of the solutions here worked for me. JSON.stringify seems to be what a lot of people say, but it cuts out functions and seems pretty broken for some objects and arrays I tried when testing it.
I made my own solution which works in Chrome at least. Posting it here so anyone that looks this up on Google can find it.
//Make an object a string that evaluates to an equivalent object
// Note that eval() seems tricky and sometimes you have to do
// something like eval("a = " + yourString), then use the value
// of a.
//
// Also this leaves extra commas after everything, but JavaScript
// ignores them.
function convertToText(obj) {
//create an array that will later be joined into a string.
var string = [];
//is object
// Both arrays and objects seem to return "object"
// when typeof(obj) is applied to them. So instead
// I am checking to see if they have the property
// join, which normal objects don't have but
// arrays do.
if (typeof(obj) == "object" && (obj.join == undefined)) {
string.push("{");
for (prop in obj) {
string.push(prop, ": ", convertToText(obj[prop]), ",");
};
string.push("}");
//is array
} else if (typeof(obj) == "object" && !(obj.join == undefined)) {
string.push("[")
for(prop in obj) {
string.push(convertToText(obj[prop]), ",");
}
string.push("]")
//is function
} else if (typeof(obj) == "function") {
string.push(obj.toString())
//all other values can be done with JSON.stringify
} else {
string.push(JSON.stringify(obj))
}
return string.join("")
}
EDIT: I know this code can be improved but just never got around to doing it. User andrey suggested an improvement here with the comment:
Here is a little bit changed code, which can handle 'null' and 'undefined', and also do not add excessive commas.
Use that at your own risk as I haven't verified it at all. Feel free to suggest any additional improvements as a comment.
If you're just outputting to the console, you can use console.log('string:', obj). Notice the comma.
In cases where you know the object is just a Boolean, Date, String, number etc... The javascript String() function works just fine. I recently found this useful in dealing with values coming from jquery's $.each function.
For example the following would convert all items in "value" to a string:
$.each(this, function (name, value) {
alert(String(value));
});
More details here:
http://www.w3schools.com/jsref/jsref_string.asp
I was looking for this, and wrote a deep recursive one with indentation :
function objToString(obj, ndeep) {
if(obj == null){ return String(obj); }
switch(typeof obj){
case "string": return '"'+obj+'"';
case "function": return obj.name || obj.toString();
case "object":
var indent = Array(ndeep||1).join('\t'), isArray = Array.isArray(obj);
return '{['[+isArray] + Object.keys(obj).map(function(key){
return '\n\t' + indent + key + ': ' + objToString(obj[key], (ndeep||1)+1);
}).join(',') + '\n' + indent + '}]'[+isArray];
default: return obj.toString();
}
}
Usage : objToString({ a: 1, b: { c: "test" } })
var obj={
name:'xyz',
Address:'123, Somestreet'
}
var convertedString=JSON.stringify(obj)
console.log("literal object is",obj ,typeof obj);
console.log("converted string :",convertedString);
console.log(" convertedString type:",typeof convertedString);
There is actually one easy option (for recent browsers and Node.js) missing in the existing answers:
console.log('Item: %o', o);
I would prefer this as JSON.stringify() has certain limitations (e.g. with circular structures).
If you just want to see the object for debugging, you can use
var o = {a:1, b:2}
console.dir(o)
1.
JSON.stringify(o);
Item: {"a":"1", "b":"2"}
2.
var o = {a:1, b:2};
var b=[]; Object.keys(o).forEach(function(k){b.push(k+":"+o[k]);});
b="{"+b.join(', ')+"}";
console.log('Item: ' + b);
Item: {a:1, b:2}
It appears JSON accept the second parameter that could help with functions - replacer, this solves the issue of converting in the most elegant way:
JSON.stringify(object, (key, val) => {
if (typeof val === 'function') {
return String(val);
}
return val;
});
JSON methods are quite inferior to the Gecko engine .toSource() primitive.
See the SO article response for comparison tests.
Also, the answer above refers to http://forums.devshed.com/javascript-development-115/tosource-with-arrays-in-ie-386109.html which, like JSON, (which the other article http://www.davidpirek.com/blog/object-to-string-how-to-deserialize-json uses via "ExtJs JSON encode source code") cannot handle circular references and is incomplete. The code below shows it's (spoof's) limitations (corrected to handle arrays and objects without content).
(direct link to code in //forums.devshed.com/ ... /tosource-with-arrays-in-ie-386109)
javascript:
Object.prototype.spoof=function(){
if (this instanceof String){
return '(new String("'+this.replace(/"/g, '\\"')+'"))';
}
var str=(this instanceof Array)
? '['
: (this instanceof Object)
? '{'
: '(';
for (var i in this){
if (this[i] != Object.prototype.spoof) {
if (this instanceof Array == false) {
str+=(i.match(/\W/))
? '"'+i.replace('"', '\\"')+'":'
: i+':';
}
if (typeof this[i] == 'string'){
str+='"'+this[i].replace('"', '\\"');
}
else if (this[i] instanceof Date){
str+='new Date("'+this[i].toGMTString()+'")';
}
else if (this[i] instanceof Array || this[i] instanceof Object){
str+=this[i].spoof();
}
else {
str+=this[i];
}
str+=', ';
}
};
str=/* fix */(str.length>2?str.substring(0, str.length-2):str)/* -ed */+(
(this instanceof Array)
? ']'
: (this instanceof Object)
? '}'
: ')'
);
return str;
};
for(i in objRA=[
[ 'Simple Raw Object source code:',
'[new Array, new Object, new Boolean, new Number, ' +
'new String, new RegExp, new Function, new Date]' ] ,
[ 'Literal Instances source code:',
'[ [], {}, true, 1, "", /./, function(){}, new Date() ]' ] ,
[ 'some predefined entities:',
'[JSON, Math, null, Infinity, NaN, ' +
'void(0), Function, Array, Object, undefined]' ]
])
alert([
'\n\n\ntesting:',objRA[i][0],objRA[i][1],
'\n.toSource()',(obj=eval(objRA[i][1])).toSource(),
'\ntoSource() spoof:',obj.spoof()
].join('\n'));
which displays:
testing:
Simple Raw Object source code:
[new Array, new Object, new Boolean, new Number, new String,
new RegExp, new Function, new Date]
.toSource()
[[], {}, (new Boolean(false)), (new Number(0)), (new String("")),
/(?:)/, (function anonymous() {}), (new Date(1303248037722))]
toSource() spoof:
[[], {}, {}, {}, (new String("")),
{}, {}, new Date("Tue, 19 Apr 2011 21:20:37 GMT")]
and
testing:
Literal Instances source code:
[ [], {}, true, 1, "", /./, function(){}, new Date() ]
.toSource()
[[], {}, true, 1, "", /./, (function () {}), (new Date(1303248055778))]
toSource() spoof:
[[], {}, true, 1, ", {}, {}, new Date("Tue, 19 Apr 2011 21:20:55 GMT")]
and
testing:
some predefined entities:
[JSON, Math, null, Infinity, NaN, void(0), Function, Array, Object, undefined]
.toSource()
[JSON, Math, null, Infinity, NaN, (void 0),
function Function() {[native code]}, function Array() {[native code]},
function Object() {[native code]}, (void 0)]
toSource() spoof:
[{}, {}, null, Infinity, NaN, undefined, {}, {}, {}, undefined]
For non-nested objects:
Object.entries(o).map(x=>x.join(":")).join("\r\n")
stringify-object is a good npm library made by the yeoman team: https://www.npmjs.com/package/stringify-object
npm install stringify-object
then:
const stringifyObject = require('stringify-object');
stringifyObject(myCircularObject);
Obviously it's interesting only if you have circular object that would fail with JSON.stringify();
As firefox does not stringify some object as screen object ; if you want to have the same result such as : JSON.stringify(obj) :
function objToString (obj) {
var tabjson=[];
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
tabjson.push('"'+p +'"'+ ':' + obj[p]);
}
} tabjson.push()
return '{'+tabjson.join(',')+'}';
}
If you only care about strings, objects, and arrays:
function objectToString (obj) {
var str = '';
var i=0;
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if(typeof obj[key] == 'object')
{
if(obj[key] instanceof Array)
{
str+= key + ' : [ ';
for(var j=0;j<obj[key].length;j++)
{
if(typeof obj[key][j]=='object') {
str += '{' + objectToString(obj[key][j]) + (j > 0 ? ',' : '') + '}';
}
else
{
str += '\'' + obj[key][j] + '\'' + (j > 0 ? ',' : ''); //non objects would be represented as strings
}
}
str+= ']' + (i > 0 ? ',' : '')
}
else
{
str += key + ' : { ' + objectToString(obj[key]) + '} ' + (i > 0 ? ',' : '');
}
}
else {
str +=key + ':\'' + obj[key] + '\'' + (i > 0 ? ',' : '');
}
i++;
}
}
return str;
}
maybe you are looking for
JSON.stringify(JSON.stringify(obj))
"{\"id\":30}"
Take a look at the jQuery-JSON plugin
At its core, it uses JSON.stringify but falls back to its own parser if the browser doesn't implement it.
If you can use lodash you can do it this way:
> var o = {a:1, b:2};
> '{' + _.map(o, (value, key) => key + ':' + value).join(', ') + '}'
'{a:1, b:2}'
With lodash map() you can iterate over Objects as well.
This maps every key/value entry to its string representation:
> _.map(o, (value, key) => key + ':' + value)
[ 'a:1', 'b:2' ]
And join() put the array entries together.
If you can use ES6 Template String, this works also:
> `{${_.map(o, (value, key) => `${key}:${value}`).join(', ')}}`
'{a:1, b:2}'
Please note this do not goes recursive through the Object:
> var o = {a:1, b:{c:2}}
> _.map(o, (value, key) => `${key}:${value}`)
[ 'a:1', 'b:[object Object]' ]
Like node's util.inspect() will do:
> util.inspect(o)
'{ a: 1, b: { c: 2 } }'
function objToString (obj) {
var str = '{';
if(typeof obj=='object')
{
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
str += p + ':' + objToString (obj[p]) + ',';
}
}
}
else
{
if(typeof obj=='string')
{
return '"'+obj+'"';
}
else
{
return obj+'';
}
}
return str.substring(0,str.length-1)+"}";
}
var o = {a:1, b:2};
o.toString=function(){
return 'a='+this.a+', b='+this.b;
};
console.log(o);
console.log('Item: ' + o);
Since Javascript v1.0 works everywhere (even IE)
this is a native approach and allows for a very costomised look of your object while debugging and in production
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
Usefull example
var Ship=function(n,x,y){
this.name = n;
this.x = x;
this.y = y;
};
Ship.prototype.toString=function(){
return '"'+this.name+'" located at: x:'+this.x+' y:'+this.y;
};
alert([new Ship('Star Destroyer', 50.001, 53.201),
new Ship('Millennium Falcon', 123.987, 287.543),
new Ship('TIE fighter', 83.060, 102.523)].join('\n'));//now they can battle!
//"Star Destroyer" located at: x:50.001 y:53.201
//"Millennium Falcon" located at: x:123.987 y:287.543
//"TIE fighter" located at: x:83.06 y:102.523
Also, as a bonus
function ISO8601Date(){
return this.getFullYear()+'-'+(this.getMonth()+1)+'-'+this.getDate();
}
var d=new Date();
d.toString=ISO8601Date;//demonstrates altering native object behaviour
alert(d);
//IE6 Fri Jul 29 04:21:26 UTC+1200 2016
//FF&GC Fri Jul 29 2016 04:21:26 GMT+1200 (New Zealand Standard Time)
//d.toString=ISO8601Date; 2016-7-29
Circular References
By using below replacer we can produce less redundant JSON - if source object contains multi-references to some object, or contains circular references - then we reference it by special path-string (similar to JSONPath) - we use it as follows
let s = JSON.stringify(obj, refReplacer());
function refReplacer() {
let m = new Map(), v= new Map(), init = null;
return function(field, value) {
let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
let isComplex= value===Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/,'');
let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2: a }, b, a }; // duplicate reference
a.a3 = [1,2,b]; // circular reference
b.b3 = a; // circular reference
let s = JSON.stringify(obj, refReplacer(), 4);
console.log(s);
BONUS: and here is inverse function of such serialisation
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ------------
// TEST
// ------------
let s = `{
"o1": {
"o2": {
"a1": 1,
"a2": 2,
"a3": [
1,
2,
{
"b1": 3,
"b2": "4",
"b3": "#REF:$.o1.o2"
}
]
}
},
"b": "#REF:$.o1.o2.a3[2]",
"a": "#REF:$.o1.o2"
}`;
console.log('Open Chrome console to see nested fields:');
let obj = parseRefJSON(s);
console.log(obj);
/*
This function is as JSON.Stringify (but if you has not in your js-engine you can use this)
Params:
obj - your object
inc_ident - can be " " or "\t".
show_types - show types of object or not
ident - need for recoursion but you can not set this parameter.
*/
function getAsText(obj, inc_ident, show_types, ident) {
var res = "";
if (!ident)
ident = "";
if (typeof(obj) == "string") {
res += "\"" + obj + "\" ";
res += (show_types == true) ? "/* typeobj: " + typeof(obj) + "*/" : "";
} else if (typeof(obj) == "number" || typeof(obj) == "boolean") {
res += obj;
res += (show_types == true) ? "/* typeobj: " + typeof(obj) + "*/" : "";
} else if (obj instanceof Array) {
res += "[ ";
res += show_types ? "/* typeobj: " + typeof(obj) + "*/" : "";
res += "\r\n";
var new_ident = ident + inc_ident;
var arr = [];
for(var key in obj) {
arr.push(new_ident + getAsText(obj[key], inc_ident, show_types, new_ident));
}
res += arr.join(",\r\n") + "\r\n";
res += ident + "]";
} else {
var new_ident = ident + inc_ident;
res += "{ ";
res += (show_types == true) ? "/* typeobj: " + typeof(obj) + "*/" : "";
res += "\r\n";
var arr = [];
for(var key in obj) {
arr.push(new_ident + '"' + key + "\" : " + getAsText(obj[key], inc_ident, show_types, new_ident));
}
res += arr.join(",\r\n") + "\r\n";
res += ident + "}\r\n";
}
return res;
};
example to use:
var obj = {
str : "hello",
arr : ["1", "2", "3", 4],
b : true,
vobj : {
str : "hello2"
}
}
var ForReading = 1, ForWriting = 2;
var fso = new ActiveXObject("Scripting.FileSystemObject")
f1 = fso.OpenTextFile("your_object1.txt", ForWriting, true)
f1.Write(getAsText(obj, "\t"));
f1.Close();
f2 = fso.OpenTextFile("your_object2.txt", ForWriting, true)
f2.Write(getAsText(obj, "\t", true));
f2.Close();
your_object1.txt:
{
"str" : "hello" ,
"arr" : [
"1" ,
"2" ,
"3" ,
4
],
"b" : true,
"vobj" : {
"str" : "hello2"
}
}
your_object2.txt:
{ /* typeobj: object*/
"str" : "hello" /* typeobj: string*/,
"arr" : [ /* typeobj: object*/
"1" /* typeobj: string*/,
"2" /* typeobj: string*/,
"3" /* typeobj: string*/,
4/* typeobj: number*/
],
"b" : true/* typeobj: boolean*/,
"vobj" : { /* typeobj: object*/
"str" : "hello2" /* typeobj: string*/
}
}
For your example, I think
console.log("Item:",o)
would be easiest. But,
console.log("Item:" + o.toString)
would also work.
Using method number one uses a nice dropdown in the console, so a long object would work nicely.
I hope this example will help for all those who all are working on array of objects
var data_array = [{
"id": "0",
"store": "ABC"
},{
"id":"1",
"store":"XYZ"
}];
console.log(String(data_array[1]["id"]+data_array[1]["store"]));
If you wont aplay join() to Object.
const obj = {one:1, two:2, three:3};
let arr = [];
for(let p in obj)
arr.push(obj[p]);
const str = arr.join(',');
Related
Is there a fast and simple way to encode a JavaScript object into a string that I can pass via a GET request?
No jQuery, no other frameworks—just plain JavaScript :)
Like this:
serialize = function(obj) {
var str = [];
for (var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
console.log(serialize({
foo: "hi there",
bar: "100%"
}));
// foo=hi%20there&bar=100%25
This one also converts recursive objects (using PHP "array" notation for the query string):
serialize = function(obj, prefix) {
var str = [],
p;
for (p in obj) {
if (obj.hasOwnProperty(p)) {
var k = prefix ? prefix + "[" + p + "]" : p,
v = obj[p];
str.push((v !== null && typeof v === "object") ?
serialize(v, k) :
encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
}
return str.join("&");
}
console.log(serialize({
foo: "hi there",
bar: {
blah: 123,
quux: [1, 2, 3]
}
}));
// foo=hi%20there&bar%5Bblah%5D=123&bar%5Bquux%5D%5B0%5D=1&bar%5Bquux%5D%5B1%5D=2&bar%5Bquux%5D%5B2%5D=3
Just use URLSearchParams This works in all current browsers
new URLSearchParams(object).toString()
jQuery has a function for this, jQuery.param(). If you're already using it, you can use this:
Example:
var params = { width:1680, height:1050 };
var str = jQuery.param( params );
str now contains width=1680&height=1050.
I suggest using the URLSearchParams interface:
const searchParams = new URLSearchParams();
const params = {foo: "hi there", bar: "100%" };
Object.keys(params).forEach(key => searchParams.append(key, params[key]));
console.log(searchParams.toString())
Or by passing the search object into the constructor like this:
const params = {foo: "hi there", bar: "100%" };
const queryString = new URLSearchParams(params).toString();
console.log(queryString);
Use:
Object.keys(obj).reduce(function(a,k){a.push(k+'='+encodeURIComponent(obj[k]));return a},[]).join('&')
I like this one-liner, but I bet it would be a more popular answer if it matched the accepted answer semantically:
function serialize( obj ) {
let str = '?' + Object.keys(obj).reduce(function(a, k){
a.push(k + '=' + encodeURIComponent(obj[k]));
return a;
}, []).join('&');
return str;
}
Here's a one liner in ES6:
Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`).join('&');
With Node.js v6.6.3
const querystring = require('querystring')
const obj = {
foo: 'bar',
baz: 'tor'
}
let result = querystring.stringify(obj)
// foo=bar&baz=tor
Reference: Query string
Ruby on Rails and PHP style query builder
This method converts a JavaScript object into a URI query string. It also handles nested arrays and objects (in Ruby on Rails and PHP syntax):
function serializeQuery(params, prefix) {
const query = Object.keys(params).map((key) => {
const value = params[key];
if (params.constructor === Array)
key = `${prefix}[]`;
else if (params.constructor === Object)
key = (prefix ? `${prefix}[${key}]` : key);
if (typeof value === 'object')
return serializeQuery(value, key);
else
return `${key}=${encodeURIComponent(value)}`;
});
return [].concat.apply([], query).join('&');
}
Example Usage:
let params = {
a: 100,
b: 'has spaces',
c: [1, 2, 3],
d: { x: 9, y: 8}
}
serializeQuery(params)
// returns 'a=100&b=has%20spaces&c[]=1&c[]=2&c[]=3&d[x]=9&d[y]=8
A small amendment to the accepted solution by user187291:
serialize = function(obj) {
var str = [];
for(var p in obj){
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
}
return str.join("&");
}
Checking for hasOwnProperty on the object makes JSLint and JSHint happy, and it prevents accidentally serializing methods of the object or other stuff if the object is anything but a simple dictionary. See the paragraph on for statements on Code Conventions for the JavaScript Programming Language.
Well, everyone seems to put his one-liner here so here goes mine:
const encoded = Object.entries(obj).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
If you need to send arbitrary objects, then GET is a bad idea since there are limits to the lengths of URLs that user agents and web servers will accepts. My suggestion would be to build up an array of name-value pairs to send and then build up a query string:
function QueryStringBuilder() {
var nameValues = [];
this.add = function(name, value) {
nameValues.push( {name: name, value: value} );
};
this.toQueryString = function() {
var segments = [], nameValue;
for (var i = 0, len = nameValues.length; i < len; i++) {
nameValue = nameValues[i];
segments[i] = encodeURIComponent(nameValue.name) + "=" + encodeURIComponent(nameValue.value);
}
return segments.join("&");
};
}
var qsb = new QueryStringBuilder();
qsb.add("veg", "cabbage");
qsb.add("vegCount", "5");
alert( qsb.toQueryString() );
A little bit look better
objectToQueryString(obj, prefix) {
return Object.keys(obj).map(objKey => {
if (obj.hasOwnProperty(objKey)) {
const key = prefix ? `${prefix}[${objKey}]` : objKey;
const value = obj[objKey];
return typeof value === "object" ?
this.objectToQueryString(value, key) :
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
}
return null;
}).join("&");
}
This one skips null/undefined values
export function urlEncodeQueryParams(data) {
const params = Object.keys(data).map(key => data[key] ? `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}` : '');
return params.filter(value => !!value).join('&');
}
Here's the CoffeeScript version of the accepted answer.
serialize = (obj, prefix) ->
str = []
for p, v of obj
k = if prefix then prefix + "[" + p + "]" else p
if typeof v == "object"
str.push(serialize(v, k))
else
str.push(encodeURIComponent(k) + "=" + encodeURIComponent(v))
str.join("&")
Here's a concise & recursive version with Object.entries. It handles arbitrarily nested arrays, but not nested objects. It also removes empty elements:
const format = (k,v) => v !== null ? `${k}=${encodeURIComponent(v)}` : ''
const to_qs = (obj) => {
return [].concat(...Object.entries(obj)
.map(([k,v]) => Array.isArray(v)
? v.map(arr => to_qs({[k]:arr}))
: format(k,v)))
.filter(x => x)
.join('&');
}
E.g.:
let json = {
a: [1, 2, 3],
b: [], // omit b
c: 1,
d: "test&encoding", // uriencode
e: [[4,5],[6,7]], // flatten this
f: null, // omit nulls
g: 0
};
let qs = to_qs(json)
=> "a=1&a=2&a=3&c=1&d=test%26encoding&e=4&e=5&e=6&e=7&g=0"
Use:
const toQueryString = obj => "?".concat(Object.keys(obj).map(e => `${encodeURIComponent(e)}=${encodeURIComponent(obj[e])}`).join("&"));
const data = {
offset: 5,
limit: 10
};
toQueryString(data); // => ?offset=5&limit=10
Or use a predefined feature
const data = {
offset: 5,
limit: 10
};
new URLSearchParams(data).toString(); // => ?offset=5&limit=10
Note
Both the above methods will set the value as null if not present.
If you want not to set the query parameter if value is null then use:
const toQueryString = obj => "?".concat(Object.keys(obj).map(e => obj[e] ? `${encodeURIComponent(e)}=${encodeURIComponent(obj[e])}` : null).filter(e => !!e).join("&"));
const data = {
offset: null,
limit: 10
};
toQueryString(data); // => "?limit=10" else with above methods "?offset=null&limit=10"
You can freely use any method.
In ES7 you can write this in one line:
const serialize = (obj) => (Object.entries(obj).map(i => [i[0], encodeURIComponent(i[1])].join('=')).join('&'))
I have a simpler solution that does not use any third-party library and is already apt to be used in any browser that has "Object.keys" (aka all modern browsers + edge + ie):
In ES5
function(a){
if( typeof(a) !== 'object' )
return '';
return `?${Object.keys(a).map(k=>`${k}=${a[k]}`).join('&')}`;
}
In ES3
function(a){
if( typeof(a) !== 'object' )
return '';
return '?' + Object.keys(a).map(function(k){ return k + '=' + a[k] }).join('&');
}
I made a comparison of JSON stringifiers and the results are as follows:
JSON: {"_id":"5973782bdb9a930533b05cb2","isActive":true,"balance":"$1,446.35","age":32,"name":"Logan Keller","email":"logankeller#artiq.com","phone":"+1 (952) 533-2258","friends":[{"id":0,"name":"Colon Salazar"},{"id":1,"name":"French Mcneil"},{"id":2,"name":"Carol Martin"}],"favoriteFruit":"banana"}
Rison: (_id:'5973782bdb9a930533b05cb2',age:32,balance:'$1,446.35',email:'logankeller#artiq.com',favoriteFruit:banana,friends:!((id:0,name:'Colon Salazar'),(id:1,name:'French Mcneil'),(id:2,name:'Carol Martin')),isActive:!t,name:'Logan Keller',phone:'+1 (952) 533-2258')
O-Rison: _id:'5973782bdb9a930533b05cb2',age:32,balance:'$1,446.35',email:'logankeller#artiq.com',favoriteFruit:banana,friends:!((id:0,name:'Colon Salazar'),(id:1,name:'French Mcneil'),(id:2,name:'Carol Martin')),isActive:!t,name:'Logan Keller',phone:'+1 (952) 533-2258'
JSURL: ~(_id~'5973782bdb9a930533b05cb2~isActive~true~balance~'!1*2c446.35~age~32~name~'Logan*20Keller~email~'logankeller*40artiq.com~phone~'*2b1*20*28952*29*20533-2258~friends~(~(id~0~name~'Colon*20Salazar)~(id~1~name~'French*20Mcneil)~(id~2~name~'Carol*20Martin))~favoriteFruit~'banana)
QS: _id=5973782bdb9a930533b05cb2&isActive=true&balance=$1,446.35&age=32&name=Logan Keller&email=logankeller#artiq.com&phone=+1 (952) 533-2258&friends[0][id]=0&friends[0][name]=Colon Salazar&friends[1][id]=1&friends[1][name]=French Mcneil&friends[2][id]=2&friends[2][name]=Carol Martin&favoriteFruit=banana
URLON: $_id=5973782bdb9a930533b05cb2&isActive:true&balance=$1,446.35&age:32&name=Logan%20Keller&email=logankeller#artiq.com&phone=+1%20(952)%20533-2258&friends#$id:0&name=Colon%20Salazar;&$id:1&name=French%20Mcneil;&$id:2&name=Carol%20Martin;;&favoriteFruit=banana
QS-JSON: isActive=true&balance=%241%2C446.35&age=32&name=Logan+Keller&email=logankeller%40artiq.com&phone=%2B1+(952)+533-2258&friends(0).id=0&friends(0).name=Colon+Salazar&friends(1).id=1&friends(1).name=French+Mcneil&friends(2).id=2&friends(2).name=Carol+Martin&favoriteFruit=banana
The shortest among them is URL Object Notation.
There another popular library, qs. You can add it by:
yarn add qs
And then use it like this:
import qs from 'qs'
const array = { a: { b: 'c' } }
const stringified = qs.stringify(array, { encode: false })
console.log(stringified) //-- outputs a[b]=c
ES6 solution for query string encoding of a JavaScript object
const params = {
a: 1,
b: 'query stringify',
c: null,
d: undefined,
f: '',
g: { foo: 1, bar: 2 },
h: ['Winterfell', 'Westeros', 'Braavos'],
i: { first: { second: { third: 3 }}}
}
static toQueryString(params = {}, prefix) {
const query = Object.keys(params).map((k) => {
let key = k;
const value = params[key];
if (!value && (value === null || value === undefined || isNaN(value))) {
value = '';
}
switch (params.constructor) {
case Array:
key = `${prefix}[]`;
break;
case Object:
key = (prefix ? `${prefix}[${key}]` : key);
break;
}
if (typeof value === 'object') {
return this.toQueryString(value, key); // for nested objects
}
return `${key}=${encodeURIComponent(value)}`;
});
return query.join('&');
}
toQueryString(params)
"a=1&b=query%20stringify&c=&d=&f=&g[foo]=1&g[bar]=2&h[]=Winterfell&h[]=Westeros&h[]=Braavos&i[first][second][third]=3"
A single line to convert an object into a query string in case somebody needs it again:
let Objs = { a: 'obejct-a', b: 'object-b' }
Object.keys(objs).map(key => key + '=' + objs[key]).join('&')
// The result will be a=object-a&b=object-b
This is an addition for the accepted solution. This works with objects and array of objects:
parseJsonAsQueryString = function (obj, prefix, objName) {
var str = [];
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
var v = obj[p];
if (typeof v == "object") {
var k = (objName ? objName + '.' : '') + (prefix ? prefix + "[" + p + "]" : p);
str.push(parseJsonAsQueryString(v, k));
} else {
var k = (objName ? objName + '.' : '') + (prefix ? prefix + '.' + p : p);
str.push(encodeURIComponent(k) + "=" + encodeURIComponent(v));
//str.push(k + "=" + v);
}
}
}
return str.join("&");
}
Also I have added objName if you're using object parameters, like in ASP.NET MVC action methods.
If you want to convert a nested object recursively and the object may or may not contain arrays (and the arrays may contain objects or arrays, etc), then the solution gets a little more complex. This is my attempt.
I've also added some options to choose if you want to record for each object member at what depth in the main object it sits, and to choose if you want to add a label to the members that come from converted arrays.
Ideally you should test if the thing parameter really receives an object or array.
function thingToString(thing,maxDepth,recordLevel,markArrays){
//thing: object or array to be recursively serialized
//maxDepth (int or false):
// (int) how deep to go with converting objects/arrays within objs/arrays
// (false) no limit to recursive objects/arrays within objects/arrays
//recordLevel (boolean):
// true - insert "(level 1)" before transcript of members at level one (etc)
// false - just
//markArrays (boolean):
// insert text to indicate any members that came from arrays
var result = "";
if (maxDepth !== false && typeof maxDepth != 'number') {maxDepth = 3;}
var runningDepth = 0;//Keeps track how deep we're into recursion
//First prepare the function, so that it can call itself recursively
function serializeAnything(thing){
//Set path-finder values
runningDepth += 1;
if(recordLevel){result += "(level " + runningDepth + ")";}
//First convert any arrays to object so they can be processed
if (thing instanceof Array){
var realObj = {};var key;
if (markArrays) {realObj['type'] = "converted array";}
for (var i = 0;i < thing.length;i++){
if (markArrays) {key = "a" + i;} else {key = i;}
realObj[key] = thing[i];
}
thing = realObj;
console.log('converted one array to ' + typeof realObj);
console.log(thing);
}
//Then deal with it
for (var member in thing){
if (typeof thing[member] == 'object' && runningDepth < maxDepth){
serializeAnything(thing[member]);
//When a sub-object/array is serialized, it will add one to
//running depth. But when we continue to this object/array's
//next sibling, the level must go back up by one
runningDepth -= 1;
} else if (maxDepth !== false && runningDepth >= maxDepth) {
console.log('Reached bottom');
} else
if (
typeof thing[member] == "string" ||
typeof thing[member] == 'boolean' ||
typeof thing[member] == 'number'
){
result += "(" + member + ": " + thing[member] + ") ";
} else {
result += "(" + member + ": [" + typeof thing[member] + " not supported]) ";
}
}
}
//Actually kick off the serialization
serializeAnything(thing);
return result;
}
This is a solution that will work for .NET backends out of the box. I have taken the primary answer of this thread and updated it to fit our .NET needs.
function objectToQuerystring(params) {
var result = '';
function convertJsonToQueryString(data, progress, name) {
name = name || '';
progress = progress || '';
if (typeof data === 'object') {
Object.keys(data).forEach(function (key) {
var value = data[key];
if (name == '') {
convertJsonToQueryString(value, progress, key);
} else {
if (isNaN(parseInt(key))) {
convertJsonToQueryString(value, progress, name + '.' + key);
} else {
convertJsonToQueryString(value, progress, name + '[' + key+ ']');
}
}
})
} else {
result = result ? result.concat('&') : result.concat('?');
result = result.concat(`${name}=${data}`);
}
}
convertJsonToQueryString(params);
return result;
}
To do it in a better way.
It can handle recursive objects or arrays in the standard query form, like a=val&b[0]=val&b[1]=val&c=val&d[some key]=val. Here's the final function.
Logic, Functionality
const objectToQueryString = (initialObj) => {
const reducer = (obj, parentPrefix = null) => (prev, key) => {
const val = obj[key];
key = encodeURIComponent(key);
const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;
if (val == null || typeof val === 'function') {
prev.push(`${prefix}=`);
return prev;
}
if (['number', 'boolean', 'string'].includes(typeof val)) {
prev.push(`${prefix}=${encodeURIComponent(val)}`);
return prev;
}
prev.push(Object.keys(val).reduce(reducer(val, prefix), []).join('&'));
return prev;
};
return Object.keys(initialObj).reduce(reducer(initialObj), []).join('&');
};
Example
const testCase1 = {
name: 'Full Name',
age: 30
}
const testCase2 = {
name: 'Full Name',
age: 30,
children: [
{name: 'Child foo'},
{name: 'Foo again'}
],
wife: {
name: 'Very Difficult to say here'
}
}
console.log(objectToQueryString(testCase1));
console.log(objectToQueryString(testCase2));
Live Test
Expand the snippet below to verify the result in your browser -
const objectToQueryString = (initialObj) => {
const reducer = (obj, parentPrefix = null) => (prev, key) => {
const val = obj[key];
key = encodeURIComponent(key);
const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;
if (val == null || typeof val === 'function') {
prev.push(`${prefix}=`);
return prev;
}
if (['number', 'boolean', 'string'].includes(typeof val)) {
prev.push(`${prefix}=${encodeURIComponent(val)}`);
return prev;
}
prev.push(Object.keys(val).reduce(reducer(val, prefix), []).join('&'));
return prev;
};
return Object.keys(initialObj).reduce(reducer(initialObj), []).join('&');
};
const testCase1 = {
name: 'Full Name',
age: 30
}
const testCase2 = {
name: 'Full Name',
age: 30,
children: [
{name: 'Child foo'},
{name: 'Foo again'}
],
wife: {
name: 'Very Difficult to say here'
}
}
console.log(objectToQueryString(testCase1));
console.log(objectToQueryString(testCase2));
Things to consider.
It skips values for functions, null, and undefined
It skips keys and values for empty objects and arrays.
It doesn't handle Number or String objects made with new Number(1) or new String('my string') because no one should ever do that
ok, it's a older post but i'm facing this problem and i have found my personal solution.. maybe can help someone else..
function objToQueryString(obj){
var k = Object.keys(obj);
var s = "";
for(var i=0;i<k.length;i++) {
s += k[i] + "=" + encodeURIComponent(obj[k[i]]);
if (i != k.length -1) s += "&";
}
return s;
};
URLSearchParams looks good, but it didn't work for nested objects.
Try to use
encodeURIComponent(JSON.stringify(object))
The previous answers do not work if you have a lot of nested objects.
Instead you can pick the function parameter from jquery-param/jquery-param.js. It worked very well for me!
var param = function (a) {
var s = [], rbracket = /\[\]$/,
isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}, add = function (k, v) {
v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v;
s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);
}, buildParams = function (prefix, obj) {
var i, len, key;
if (prefix) {
if (isArray(obj)) {
for (i = 0, len = obj.length; i < len; i++) {
if (rbracket.test(prefix)) {
add(prefix, obj[i]);
} else {
buildParams(prefix + '[' + (typeof obj[i] === 'object' ? i : '') + ']', obj[i]);
}
}
} else if (obj && String(obj) === '[object Object]') {
for (key in obj) {
buildParams(prefix + '[' + key + ']', obj[key]);
}
} else {
add(prefix, obj);
}
} else if (isArray(obj)) {
for (i = 0, len = obj.length; i < len; i++) {
add(obj[i].name, obj[i].value);
}
} else {
for (key in obj) {
buildParams(key, obj[key]);
}
}
return s;
};
return buildParams('', a).join('&').replace(/%20/g, '+');
};
After going through some top answers here, I have wrote another implementation that tackles some edge cases as well
function serialize(params, prefix) {
return Object.entries(params).reduce((acc, [key, value]) => {
// remove whitespace from both sides of the key before encoding
key = encodeURIComponent(key.trim());
if (params.constructor === Array ) {
key = `${prefix}[]`;
} else if (params.constructor === Object) {
key = (prefix ? `${prefix}[${key}]` : key);
}
/**
* - undefined and NaN values will be skipped automatically
* - value will be empty string for functions and null
* - nested arrays will be flattened
*/
if (value === null || typeof value === 'function') {
acc.push(`${key}=`);
} else if (typeof value === 'object') {
acc = acc.concat(serialize(value, key));
} else if(['number', 'boolean', 'string'].includes(typeof value) && value === value) { // self-check to avoid NaN
acc.push(`${key}=${encodeURIComponent(value)}`);
}
return acc;
}, []);
}
function objectToQueryString(queryParameters) {
return queryParameters ? serialize(queryParameters).join('&'): '';
}
let x = objectToQueryString({
foo: 'hello world',
bar: {
blah: 123,
list: [1, 2, 3],
'nested array': [[4,5],[6,7]] // will be flattened
},
page: 1,
limit: undefined, // field will be ignored
check: false,
max: NaN, // field will be ignored
prop: null,
' key value': 'with spaces' // space in key will be trimmed out
});
console.log(x); // foo=hello%20world&bar[blah]=123&bar[list][]=1&bar[list][]=2&bar[list][]=3&bar[nested%20array][][]=4&bar[nested%20array][][]=5&bar[nested%20array][][]=6&bar[nested%20array][][]=7&page=1&check=false&prop=&key%20value=with%20spaces
How can I convert a JavaScript object into a string?
Example:
var o = {a:1, b:2}
console.log(o)
console.log('Item: ' + o)
Output:
Object { a=1, b=2} // very nice readable output :)
Item: [object Object] // no idea what's inside :(
I would recommend using JSON.stringify, which converts the set of the variables in the object to a JSON string.
var obj = {
name: 'myObj'
};
JSON.stringify(obj);
Most modern browsers support this method natively, but for those that don't, you can include a JS version.
Use javascript String() function
String(yourobject); //returns [object Object]
or stringify()
JSON.stringify(yourobject)
Sure, to convert an object into a string, you either have to use your own method, such as:
function objToString (obj) {
var str = '';
for (var p in obj) {
if (Object.prototype.hasOwnProperty.call(obj, p)) {
str += p + '::' + obj[p] + '\n';
}
}
return str;
}
Actually, the above just shows the general approach; you may wish to use something like http://phpjs.org/functions/var_export:578 or http://phpjs.org/functions/var_dump:604
or, if you are not using methods (functions as properties of your object), you may be able to use the new standard (but not implemented in older browsers, though you can find a utility to help with it for them too), JSON.stringify(). But again, that won't work if the object uses functions or other properties which aren't serializable to JSON.
Update:
A more modern solution would be:
function objToString (obj) {
let str = '';
for (const [p, val] of Object.entries(obj)) {
str += `${p}::${val}\n`;
}
return str;
}
or:
function objToString (obj) {
return Object.entries(obj).reduce((str, [p, val]) => {
return `${str}${p}::${val}\n`;
}, '');
}
Keeping it simple with console, you can just use a comma instead of a +. The + will try to convert the object into a string, whereas the comma will display it separately in the console.
Example:
var o = {a:1, b:2};
console.log(o);
console.log('Item: ' + o);
console.log('Item: ', o); // :)
Output:
Object { a=1, b=2} // useful
Item: [object Object] // not useful
Item: Object {a: 1, b: 2} // Best of both worlds! :)
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Console.log
EDIT Do not use this answer as it works only in some versions of Firefox. No other browsers support it. Use Gary Chambers solution.
toSource() is the function you are looking for which will write it out as JSON.
var object = {};
object.first = "test";
object.second = "test2";
alert(object.toSource());
One option:
console.log('Item: ' + JSON.stringify(o));
Another option (as soktinpk pointed out in the comments), and better for console debugging IMO:
console.log('Item: ', o);
None of the solutions here worked for me. JSON.stringify seems to be what a lot of people say, but it cuts out functions and seems pretty broken for some objects and arrays I tried when testing it.
I made my own solution which works in Chrome at least. Posting it here so anyone that looks this up on Google can find it.
//Make an object a string that evaluates to an equivalent object
// Note that eval() seems tricky and sometimes you have to do
// something like eval("a = " + yourString), then use the value
// of a.
//
// Also this leaves extra commas after everything, but JavaScript
// ignores them.
function convertToText(obj) {
//create an array that will later be joined into a string.
var string = [];
//is object
// Both arrays and objects seem to return "object"
// when typeof(obj) is applied to them. So instead
// I am checking to see if they have the property
// join, which normal objects don't have but
// arrays do.
if (typeof(obj) == "object" && (obj.join == undefined)) {
string.push("{");
for (prop in obj) {
string.push(prop, ": ", convertToText(obj[prop]), ",");
};
string.push("}");
//is array
} else if (typeof(obj) == "object" && !(obj.join == undefined)) {
string.push("[")
for(prop in obj) {
string.push(convertToText(obj[prop]), ",");
}
string.push("]")
//is function
} else if (typeof(obj) == "function") {
string.push(obj.toString())
//all other values can be done with JSON.stringify
} else {
string.push(JSON.stringify(obj))
}
return string.join("")
}
EDIT: I know this code can be improved but just never got around to doing it. User andrey suggested an improvement here with the comment:
Here is a little bit changed code, which can handle 'null' and 'undefined', and also do not add excessive commas.
Use that at your own risk as I haven't verified it at all. Feel free to suggest any additional improvements as a comment.
If you're just outputting to the console, you can use console.log('string:', obj). Notice the comma.
In cases where you know the object is just a Boolean, Date, String, number etc... The javascript String() function works just fine. I recently found this useful in dealing with values coming from jquery's $.each function.
For example the following would convert all items in "value" to a string:
$.each(this, function (name, value) {
alert(String(value));
});
More details here:
http://www.w3schools.com/jsref/jsref_string.asp
I was looking for this, and wrote a deep recursive one with indentation :
function objToString(obj, ndeep) {
if(obj == null){ return String(obj); }
switch(typeof obj){
case "string": return '"'+obj+'"';
case "function": return obj.name || obj.toString();
case "object":
var indent = Array(ndeep||1).join('\t'), isArray = Array.isArray(obj);
return '{['[+isArray] + Object.keys(obj).map(function(key){
return '\n\t' + indent + key + ': ' + objToString(obj[key], (ndeep||1)+1);
}).join(',') + '\n' + indent + '}]'[+isArray];
default: return obj.toString();
}
}
Usage : objToString({ a: 1, b: { c: "test" } })
var obj={
name:'xyz',
Address:'123, Somestreet'
}
var convertedString=JSON.stringify(obj)
console.log("literal object is",obj ,typeof obj);
console.log("converted string :",convertedString);
console.log(" convertedString type:",typeof convertedString);
There is actually one easy option (for recent browsers and Node.js) missing in the existing answers:
console.log('Item: %o', o);
I would prefer this as JSON.stringify() has certain limitations (e.g. with circular structures).
If you just want to see the object for debugging, you can use
var o = {a:1, b:2}
console.dir(o)
1.
JSON.stringify(o);
Item: {"a":"1", "b":"2"}
2.
var o = {a:1, b:2};
var b=[]; Object.keys(o).forEach(function(k){b.push(k+":"+o[k]);});
b="{"+b.join(', ')+"}";
console.log('Item: ' + b);
Item: {a:1, b:2}
It appears JSON accept the second parameter that could help with functions - replacer, this solves the issue of converting in the most elegant way:
JSON.stringify(object, (key, val) => {
if (typeof val === 'function') {
return String(val);
}
return val;
});
JSON methods are quite inferior to the Gecko engine .toSource() primitive.
See the SO article response for comparison tests.
Also, the answer above refers to http://forums.devshed.com/javascript-development-115/tosource-with-arrays-in-ie-386109.html which, like JSON, (which the other article http://www.davidpirek.com/blog/object-to-string-how-to-deserialize-json uses via "ExtJs JSON encode source code") cannot handle circular references and is incomplete. The code below shows it's (spoof's) limitations (corrected to handle arrays and objects without content).
(direct link to code in //forums.devshed.com/ ... /tosource-with-arrays-in-ie-386109)
javascript:
Object.prototype.spoof=function(){
if (this instanceof String){
return '(new String("'+this.replace(/"/g, '\\"')+'"))';
}
var str=(this instanceof Array)
? '['
: (this instanceof Object)
? '{'
: '(';
for (var i in this){
if (this[i] != Object.prototype.spoof) {
if (this instanceof Array == false) {
str+=(i.match(/\W/))
? '"'+i.replace('"', '\\"')+'":'
: i+':';
}
if (typeof this[i] == 'string'){
str+='"'+this[i].replace('"', '\\"');
}
else if (this[i] instanceof Date){
str+='new Date("'+this[i].toGMTString()+'")';
}
else if (this[i] instanceof Array || this[i] instanceof Object){
str+=this[i].spoof();
}
else {
str+=this[i];
}
str+=', ';
}
};
str=/* fix */(str.length>2?str.substring(0, str.length-2):str)/* -ed */+(
(this instanceof Array)
? ']'
: (this instanceof Object)
? '}'
: ')'
);
return str;
};
for(i in objRA=[
[ 'Simple Raw Object source code:',
'[new Array, new Object, new Boolean, new Number, ' +
'new String, new RegExp, new Function, new Date]' ] ,
[ 'Literal Instances source code:',
'[ [], {}, true, 1, "", /./, function(){}, new Date() ]' ] ,
[ 'some predefined entities:',
'[JSON, Math, null, Infinity, NaN, ' +
'void(0), Function, Array, Object, undefined]' ]
])
alert([
'\n\n\ntesting:',objRA[i][0],objRA[i][1],
'\n.toSource()',(obj=eval(objRA[i][1])).toSource(),
'\ntoSource() spoof:',obj.spoof()
].join('\n'));
which displays:
testing:
Simple Raw Object source code:
[new Array, new Object, new Boolean, new Number, new String,
new RegExp, new Function, new Date]
.toSource()
[[], {}, (new Boolean(false)), (new Number(0)), (new String("")),
/(?:)/, (function anonymous() {}), (new Date(1303248037722))]
toSource() spoof:
[[], {}, {}, {}, (new String("")),
{}, {}, new Date("Tue, 19 Apr 2011 21:20:37 GMT")]
and
testing:
Literal Instances source code:
[ [], {}, true, 1, "", /./, function(){}, new Date() ]
.toSource()
[[], {}, true, 1, "", /./, (function () {}), (new Date(1303248055778))]
toSource() spoof:
[[], {}, true, 1, ", {}, {}, new Date("Tue, 19 Apr 2011 21:20:55 GMT")]
and
testing:
some predefined entities:
[JSON, Math, null, Infinity, NaN, void(0), Function, Array, Object, undefined]
.toSource()
[JSON, Math, null, Infinity, NaN, (void 0),
function Function() {[native code]}, function Array() {[native code]},
function Object() {[native code]}, (void 0)]
toSource() spoof:
[{}, {}, null, Infinity, NaN, undefined, {}, {}, {}, undefined]
For non-nested objects:
Object.entries(o).map(x=>x.join(":")).join("\r\n")
stringify-object is a good npm library made by the yeoman team: https://www.npmjs.com/package/stringify-object
npm install stringify-object
then:
const stringifyObject = require('stringify-object');
stringifyObject(myCircularObject);
Obviously it's interesting only if you have circular object that would fail with JSON.stringify();
As firefox does not stringify some object as screen object ; if you want to have the same result such as : JSON.stringify(obj) :
function objToString (obj) {
var tabjson=[];
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
tabjson.push('"'+p +'"'+ ':' + obj[p]);
}
} tabjson.push()
return '{'+tabjson.join(',')+'}';
}
If you only care about strings, objects, and arrays:
function objectToString (obj) {
var str = '';
var i=0;
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if(typeof obj[key] == 'object')
{
if(obj[key] instanceof Array)
{
str+= key + ' : [ ';
for(var j=0;j<obj[key].length;j++)
{
if(typeof obj[key][j]=='object') {
str += '{' + objectToString(obj[key][j]) + (j > 0 ? ',' : '') + '}';
}
else
{
str += '\'' + obj[key][j] + '\'' + (j > 0 ? ',' : ''); //non objects would be represented as strings
}
}
str+= ']' + (i > 0 ? ',' : '')
}
else
{
str += key + ' : { ' + objectToString(obj[key]) + '} ' + (i > 0 ? ',' : '');
}
}
else {
str +=key + ':\'' + obj[key] + '\'' + (i > 0 ? ',' : '');
}
i++;
}
}
return str;
}
maybe you are looking for
JSON.stringify(JSON.stringify(obj))
"{\"id\":30}"
Take a look at the jQuery-JSON plugin
At its core, it uses JSON.stringify but falls back to its own parser if the browser doesn't implement it.
If you can use lodash you can do it this way:
> var o = {a:1, b:2};
> '{' + _.map(o, (value, key) => key + ':' + value).join(', ') + '}'
'{a:1, b:2}'
With lodash map() you can iterate over Objects as well.
This maps every key/value entry to its string representation:
> _.map(o, (value, key) => key + ':' + value)
[ 'a:1', 'b:2' ]
And join() put the array entries together.
If you can use ES6 Template String, this works also:
> `{${_.map(o, (value, key) => `${key}:${value}`).join(', ')}}`
'{a:1, b:2}'
Please note this do not goes recursive through the Object:
> var o = {a:1, b:{c:2}}
> _.map(o, (value, key) => `${key}:${value}`)
[ 'a:1', 'b:[object Object]' ]
Like node's util.inspect() will do:
> util.inspect(o)
'{ a: 1, b: { c: 2 } }'
function objToString (obj) {
var str = '{';
if(typeof obj=='object')
{
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
str += p + ':' + objToString (obj[p]) + ',';
}
}
}
else
{
if(typeof obj=='string')
{
return '"'+obj+'"';
}
else
{
return obj+'';
}
}
return str.substring(0,str.length-1)+"}";
}
var o = {a:1, b:2};
o.toString=function(){
return 'a='+this.a+', b='+this.b;
};
console.log(o);
console.log('Item: ' + o);
Since Javascript v1.0 works everywhere (even IE)
this is a native approach and allows for a very costomised look of your object while debugging and in production
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
Usefull example
var Ship=function(n,x,y){
this.name = n;
this.x = x;
this.y = y;
};
Ship.prototype.toString=function(){
return '"'+this.name+'" located at: x:'+this.x+' y:'+this.y;
};
alert([new Ship('Star Destroyer', 50.001, 53.201),
new Ship('Millennium Falcon', 123.987, 287.543),
new Ship('TIE fighter', 83.060, 102.523)].join('\n'));//now they can battle!
//"Star Destroyer" located at: x:50.001 y:53.201
//"Millennium Falcon" located at: x:123.987 y:287.543
//"TIE fighter" located at: x:83.06 y:102.523
Also, as a bonus
function ISO8601Date(){
return this.getFullYear()+'-'+(this.getMonth()+1)+'-'+this.getDate();
}
var d=new Date();
d.toString=ISO8601Date;//demonstrates altering native object behaviour
alert(d);
//IE6 Fri Jul 29 04:21:26 UTC+1200 2016
//FF&GC Fri Jul 29 2016 04:21:26 GMT+1200 (New Zealand Standard Time)
//d.toString=ISO8601Date; 2016-7-29
Circular References
By using below replacer we can produce less redundant JSON - if source object contains multi-references to some object, or contains circular references - then we reference it by special path-string (similar to JSONPath) - we use it as follows
let s = JSON.stringify(obj, refReplacer());
function refReplacer() {
let m = new Map(), v= new Map(), init = null;
return function(field, value) {
let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
let isComplex= value===Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/,'');
let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2: a }, b, a }; // duplicate reference
a.a3 = [1,2,b]; // circular reference
b.b3 = a; // circular reference
let s = JSON.stringify(obj, refReplacer(), 4);
console.log(s);
BONUS: and here is inverse function of such serialisation
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ------------
// TEST
// ------------
let s = `{
"o1": {
"o2": {
"a1": 1,
"a2": 2,
"a3": [
1,
2,
{
"b1": 3,
"b2": "4",
"b3": "#REF:$.o1.o2"
}
]
}
},
"b": "#REF:$.o1.o2.a3[2]",
"a": "#REF:$.o1.o2"
}`;
console.log('Open Chrome console to see nested fields:');
let obj = parseRefJSON(s);
console.log(obj);
/*
This function is as JSON.Stringify (but if you has not in your js-engine you can use this)
Params:
obj - your object
inc_ident - can be " " or "\t".
show_types - show types of object or not
ident - need for recoursion but you can not set this parameter.
*/
function getAsText(obj, inc_ident, show_types, ident) {
var res = "";
if (!ident)
ident = "";
if (typeof(obj) == "string") {
res += "\"" + obj + "\" ";
res += (show_types == true) ? "/* typeobj: " + typeof(obj) + "*/" : "";
} else if (typeof(obj) == "number" || typeof(obj) == "boolean") {
res += obj;
res += (show_types == true) ? "/* typeobj: " + typeof(obj) + "*/" : "";
} else if (obj instanceof Array) {
res += "[ ";
res += show_types ? "/* typeobj: " + typeof(obj) + "*/" : "";
res += "\r\n";
var new_ident = ident + inc_ident;
var arr = [];
for(var key in obj) {
arr.push(new_ident + getAsText(obj[key], inc_ident, show_types, new_ident));
}
res += arr.join(",\r\n") + "\r\n";
res += ident + "]";
} else {
var new_ident = ident + inc_ident;
res += "{ ";
res += (show_types == true) ? "/* typeobj: " + typeof(obj) + "*/" : "";
res += "\r\n";
var arr = [];
for(var key in obj) {
arr.push(new_ident + '"' + key + "\" : " + getAsText(obj[key], inc_ident, show_types, new_ident));
}
res += arr.join(",\r\n") + "\r\n";
res += ident + "}\r\n";
}
return res;
};
example to use:
var obj = {
str : "hello",
arr : ["1", "2", "3", 4],
b : true,
vobj : {
str : "hello2"
}
}
var ForReading = 1, ForWriting = 2;
var fso = new ActiveXObject("Scripting.FileSystemObject")
f1 = fso.OpenTextFile("your_object1.txt", ForWriting, true)
f1.Write(getAsText(obj, "\t"));
f1.Close();
f2 = fso.OpenTextFile("your_object2.txt", ForWriting, true)
f2.Write(getAsText(obj, "\t", true));
f2.Close();
your_object1.txt:
{
"str" : "hello" ,
"arr" : [
"1" ,
"2" ,
"3" ,
4
],
"b" : true,
"vobj" : {
"str" : "hello2"
}
}
your_object2.txt:
{ /* typeobj: object*/
"str" : "hello" /* typeobj: string*/,
"arr" : [ /* typeobj: object*/
"1" /* typeobj: string*/,
"2" /* typeobj: string*/,
"3" /* typeobj: string*/,
4/* typeobj: number*/
],
"b" : true/* typeobj: boolean*/,
"vobj" : { /* typeobj: object*/
"str" : "hello2" /* typeobj: string*/
}
}
For your example, I think
console.log("Item:",o)
would be easiest. But,
console.log("Item:" + o.toString)
would also work.
Using method number one uses a nice dropdown in the console, so a long object would work nicely.
I hope this example will help for all those who all are working on array of objects
var data_array = [{
"id": "0",
"store": "ABC"
},{
"id":"1",
"store":"XYZ"
}];
console.log(String(data_array[1]["id"]+data_array[1]["store"]));
If you wont aplay join() to Object.
const obj = {one:1, two:2, three:3};
let arr = [];
for(let p in obj)
arr.push(obj[p]);
const str = arr.join(',');
I threw some code together to flatten and un-flatten complex/nested JavaScript objects. It works, but it's a bit slow (triggers the 'long script' warning).
For the flattened names I want "." as the delimiter and [INDEX] for arrays.
Examples:
un-flattened | flattened
---------------------------
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1].[0]":2,"[1].[1].[0]":3,"[1].[1].[1]":4,"[1].[2]":5,"[2]":6}
I created a benchmark that ~simulates my use case http://jsfiddle.net/WSzec/
Get a nested object
Flatten it
Look through it and possibly modify it while flattened
Unflatten it back to it's original nested format to be shipped away
I would like faster code: For clarification, code that completes the JSFiddle benchmark (http://jsfiddle.net/WSzec/) significantly faster (~20%+ would be nice) in IE 9+, FF 24+, and Chrome 29+.
Here's the relevant JavaScript code: Current Fastest: http://jsfiddle.net/WSzec/6/
var unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp;
for(var p in data) {
cur = result, prop = "", last = 0;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
prop = temp;
last = idx + 1;
} while(idx >= 0);
cur[prop] = data[p];
}
return result[""];
}
var flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop ? prop+"."+i : ""+i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
EDIT 1 Modified the above to #Bergi 's implementation which is currently the fastest. As an aside, using ".indexOf" instead of "regex.exec" is around 20% faster in FF but 20% slower in Chrome; so I'll stick with the regex since it's simpler (here's my attempt at using indexOf to replace the regex http://jsfiddle.net/WSzec/2/).
EDIT 2 Building on #Bergi 's idea I managed to created a faster non-regex version (3x faster in FF and ~10% faster in Chrome). http://jsfiddle.net/WSzec/6/ In the this (the current) implementation the rules for key names are simply, keys cannot start with an integer or contain a period.
Example:
{"foo":{"bar":[0]}} => {"foo.bar.0":0}
EDIT 3 Adding #AaditMShah 's inline path parsing approach (rather than String.split) helped to improve the unflatten performance. I'm very happy with the overall performance improvement reached.
The latest jsfiddle and jsperf:
http://jsfiddle.net/WSzec/14/
http://jsperf.com/flatten-un-flatten/4
Here's my much shorter implementation:
Object.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
resultholder = {};
for (var p in data) {
var cur = resultholder,
prop = "",
m;
while (m = regex.exec(p)) {
cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
prop = m[2] || m[1];
}
cur[prop] = data[p];
}
return resultholder[""] || resultholder;
};
flatten hasn't changed much (and I'm not sure whether you really need those isEmpty cases):
Object.flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
Together, they run your benchmark in about the half of the time (Opera 12.16: ~900ms instead of ~ 1900ms, Chrome 29: ~800ms instead of ~1600ms).
Note: This and most other solutions answered here focus on speed and are susceptible to prototype pollution and shold not be used on untrusted objects.
I wrote two functions to flatten and unflatten a JSON object.
Flatten a JSON object:
var flatten = (function (isArray, wrapped) {
return function (table) {
return reduce("", {}, table);
};
function reduce(path, accumulator, table) {
if (isArray(table)) {
var length = table.length;
if (length) {
var index = 0;
while (index < length) {
var property = path + "[" + index + "]", item = table[index++];
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else accumulator[path] = table;
} else {
var empty = true;
if (path) {
for (var property in table) {
var item = table[property], property = path + "." + property, empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else {
for (var property in table) {
var item = table[property], empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
}
if (empty) accumulator[path] = table;
}
return accumulator;
}
}(Array.isArray, Object));
Performance:
It's faster than the current solution in Opera. The current solution is 26% slower in Opera.
It's faster than the current solution in Firefox. The current solution is 9% slower in Firefox.
It's faster than the current solution in Chrome. The current solution is 29% slower in Chrome.
Unflatten a JSON object:
function unflatten(table) {
var result = {};
for (var path in table) {
var cursor = result, length = path.length, property = "", index = 0;
while (index < length) {
var char = path.charAt(index);
if (char === "[") {
var start = index + 1,
end = path.indexOf("]", start),
cursor = cursor[property] = cursor[property] || [],
property = path.slice(start, end),
index = end + 1;
} else {
var cursor = cursor[property] = cursor[property] || {},
start = char === "." ? index + 1 : index,
bracket = path.indexOf("[", start),
dot = path.indexOf(".", start);
if (bracket < 0 && dot < 0) var end = index = length;
else if (bracket < 0) var end = index = dot;
else if (dot < 0) var end = index = bracket;
else var end = index = bracket < dot ? bracket : dot;
var property = path.slice(start, end);
}
}
cursor[property] = table[path];
}
return result[""];
}
Performance:
It's faster than the current solution in Opera. The current solution is 5% slower in Opera.
It's slower than the current solution in Firefox. My solution is 26% slower in Firefox.
It's slower than the current solution in Chrome. My solution is 6% slower in Chrome.
Flatten and unflatten a JSON object:
Overall my solution performs either equally well or even better than the current solution.
Performance:
It's faster than the current solution in Opera. The current solution is 21% slower in Opera.
It's as fast as the current solution in Firefox.
It's faster than the current solution in Firefox. The current solution is 20% slower in Chrome.
Output format:
A flattened object uses the dot notation for object properties and the bracket notation for array indices:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}
In my opinion this format is better than only using the dot notation:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a.0.b.0":"c","a.0.b.1":"d"}
[1,[2,[3,4],5],6] => {"0":1,"1.0":2,"1.1.0":3,"1.1.1":4,"1.2":5,"2":6}
Advantages:
Flattening an object is faster than the current solution.
Flattening and unflattening an object is as fast as or faster than the current solution.
Flattened objects use both the dot notation and the bracket notation for readability.
Disadvantages:
Unflattening an object is slower than the current solution in most (but not all) cases.
The current JSFiddle demo gave the following values as output:
Nested : 132175 : 63
Flattened : 132175 : 564
Nested : 132175 : 54
Flattened : 132175 : 508
My updated JSFiddle demo gave the following values as output:
Nested : 132175 : 59
Flattened : 132175 : 514
Nested : 132175 : 60
Flattened : 132175 : 451
I'm not really sure what that means, so I'll stick with the jsPerf results. After all jsPerf is a performance benchmarking utility. JSFiddle is not.
ES6 version:
const flatten = (obj, path = '') => {
if (!(obj instanceof Object)) return {[path.replace(/\.$/g, '')]:obj};
return Object.keys(obj).reduce((output, key) => {
return obj instanceof Array ?
{...output, ...flatten(obj[key], path + '[' + key + '].')}:
{...output, ...flatten(obj[key], path + key + '.')};
}, {});
}
Example:
console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
3 ½ Years later...
For my own project I wanted to flatten JSON objects in mongoDB dot notation and came up with a simple solution:
/**
* Recursively flattens a JSON object using dot notation.
*
* NOTE: input must be an object as described by JSON spec. Arbitrary
* JS objects (e.g. {a: () => 42}) may result in unexpected output.
* MOREOVER, it removes keys with empty objects/arrays as value (see
* examples bellow).
*
* #example
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e': 3, 'b.1': 4}
* flatten({a: 1, b: [{c: 2, d: {e: 3}}, 4]})
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e.0': true, 'b.0.d.e.1': false, 'b.0.d.e.2.f': 1}
* flatten({a: 1, b: [{c: 2, d: {e: [true, false, {f: 1}]}}]})
* // return {a: 1}
* flatten({a: 1, b: [], c: {}})
*
* #param obj item to be flattened
* #param {Array.string} [prefix=[]] chain of prefix joined with a dot and prepended to key
* #param {Object} [current={}] result of flatten during the recursion
*
* #see https://docs.mongodb.com/manual/core/document/#dot-notation
*/
function flatten (obj, prefix, current) {
prefix = prefix || []
current = current || {}
// Remember kids, null is also an object!
if (typeof (obj) === 'object' && obj !== null) {
Object.keys(obj).forEach(key => {
this.flatten(obj[key], prefix.concat(key), current)
})
} else {
current[prefix.join('.')] = obj
}
return current
}
Features and/or caveats
It only accepts JSON objects. So if you pass something like {a: () => {}} you might not get what you wanted!
It removes empty arrays and objects. So this {a: {}, b: []} is flattened to {}.
Use this library:
npm install flat
Usage (from https://www.npmjs.com/package/flat):
Flatten:
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
Un-flatten:
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
Here's another approach that runs slower (about 1000ms) than the above answer, but has an interesting idea :-)
Instead of iterating through each property chain, it just picks the last property and uses a look-up-table for the rest to store the intermediate results. This look-up-table will be iterated until there are no property chains left and all values reside on uncocatenated properties.
JSON.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)$|\[(\d+)\]$/,
props = Object.keys(data),
result, p;
while(p = props.shift()) {
var m = regex.exec(p),
target;
if (m.index) {
var rest = p.slice(0, m.index);
if (!(rest in data)) {
data[rest] = m[2] ? [] : {};
props.push(rest);
}
target = data[rest];
} else {
target = result || (result = (m[2] ? [] : {}));
}
target[m[2] || m[1]] = data[p];
}
return result;
};
It currently uses the data input parameter for the table, and puts lots of properties on it - a non-destructive version should be possible as well. Maybe a clever lastIndexOf usage performs better than the regex (depends on the regex engine).
See it in action here.
You can use https://github.com/hughsk/flat
Take a nested Javascript object and flatten it, or unflatten an object with delimited keys.
Example from the doc
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
This code recursively flattens out JSON objects.
I included my timing mechanism in the code and it gives me 1ms but I'm not sure if that's the most accurate one.
var new_json = [{
"name": "fatima",
"age": 25,
"neighbour": {
"name": "taqi",
"location": "end of the street",
"property": {
"built in": 1990,
"owned": false,
"years on market": [1990, 1998, 2002, 2013],
"year short listed": [], //means never
}
},
"town": "Mountain View",
"state": "CA"
},
{
"name": "qianru",
"age": 20,
"neighbour": {
"name": "joe",
"location": "opposite to the park",
"property": {
"built in": 2011,
"owned": true,
"years on market": [1996, 2011],
"year short listed": [], //means never
}
},
"town": "Pittsburgh",
"state": "PA"
}]
function flatten(json, flattened, str_key) {
for (var key in json) {
if (json.hasOwnProperty(key)) {
if (json[key] instanceof Object && json[key] != "") {
flatten(json[key], flattened, str_key + "." + key);
} else {
flattened[str_key + "." + key] = json[key];
}
}
}
}
var flattened = {};
console.time('flatten');
flatten(new_json, flattened, "");
console.timeEnd('flatten');
for (var key in flattened){
console.log(key + ": " + flattened[key]);
}
Output:
flatten: 1ms
.0.name: fatima
.0.age: 25
.0.neighbour.name: taqi
.0.neighbour.location: end of the street
.0.neighbour.property.built in: 1990
.0.neighbour.property.owned: false
.0.neighbour.property.years on market.0: 1990
.0.neighbour.property.years on market.1: 1998
.0.neighbour.property.years on market.2: 2002
.0.neighbour.property.years on market.3: 2013
.0.neighbour.property.year short listed:
.0.town: Mountain View
.0.state: CA
.1.name: qianru
.1.age: 20
.1.neighbour.name: joe
.1.neighbour.location: opposite to the park
.1.neighbour.property.built in: 2011
.1.neighbour.property.owned: true
.1.neighbour.property.years on market.0: 1996
.1.neighbour.property.years on market.1: 2011
.1.neighbour.property.year short listed:
.1.town: Pittsburgh
.1.state: PA
Here's mine. It runs in <2ms in Google Apps Script on a sizable object. It uses dashes instead of dots for separators, and it doesn't handle arrays specially like in the asker's question, but this is what I wanted for my use.
function flatten (obj) {
var newObj = {};
for (var key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
var temp = flatten(obj[key])
for (var key2 in temp) {
newObj[key+"-"+key2] = temp[key2];
}
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
Example:
var test = {
a: 1,
b: 2,
c: {
c1: 3.1,
c2: 3.2
},
d: 4,
e: {
e1: 5.1,
e2: 5.2,
e3: {
e3a: 5.31,
e3b: 5.32
},
e4: 5.4
},
f: 6
}
Logger.log("start");
Logger.log(JSON.stringify(flatten(test),null,2));
Logger.log("done");
Example output:
[17-02-08 13:21:05:245 CST] start
[17-02-08 13:21:05:246 CST] {
"a": 1,
"b": 2,
"c-c1": 3.1,
"c-c2": 3.2,
"d": 4,
"e-e1": 5.1,
"e-e2": 5.2,
"e-e3-e3a": 5.31,
"e-e3-e3b": 5.32,
"e-e4": 5.4,
"f": 6
}
[17-02-08 13:21:05:247 CST] done
Object.prototype.flatten = function (obj) {
let ans = {};
let anotherObj = { ...obj };
function performFlatten(anotherObj) {
Object.keys(anotherObj).forEach((key, idx) => {
if (typeof anotherObj[key] !== 'object') {
ans[key] = anotherObj[key];
console.log('ans so far : ', ans);
} else {
console.log(key, { ...anotherObj[key] });
performFlatten(anotherObj[key]);
}
})
}
performFlatten(anotherObj);
return ans;
}
let ans = flatten(obj);
console.log(ans);
I added +/- 10-15% efficiency to the selected answer by minor code refactoring and moving the recursive function outside of the function namespace.
See my question: Are namespaced functions reevaluated on every call? for why this slows nested functions down.
function _flatten (target, obj, path) {
var i, empty;
if (obj.constructor === Object) {
empty = true;
for (i in obj) {
empty = false;
_flatten(target, obj[i], path ? path + '.' + i : i);
}
if (empty && path) {
target[path] = {};
}
}
else if (obj.constructor === Array) {
i = obj.length;
if (i > 0) {
while (i--) {
_flatten(target, obj[i], path + '[' + i + ']');
}
} else {
target[path] = [];
}
}
else {
target[path] = obj;
}
}
function flatten (data) {
var result = {};
_flatten(result, data, null);
return result;
}
See benchmark.
Here's a recursive solution for flatten I put together in PowerShell:
#---helper function for ConvertTo-JhcUtilJsonTable
#
function getNodes {
param (
[Parameter(Mandatory)]
[System.Object]
$job,
[Parameter(Mandatory)]
[System.String]
$path
)
$t = $job.GetType()
$ct = 0
$h = #{}
if ($t.Name -eq 'PSCustomObject') {
foreach ($m in Get-Member -InputObject $job -MemberType NoteProperty) {
getNodes -job $job.($m.Name) -path ($path + '.' + $m.Name)
}
}
elseif ($t.Name -eq 'Object[]') {
foreach ($o in $job) {
getNodes -job $o -path ($path + "[$ct]")
$ct++
}
}
else {
$h[$path] = $job
$h
}
}
#---flattens a JSON document object into a key value table where keys are proper JSON paths corresponding to their value
#
function ConvertTo-JhcUtilJsonTable {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Object[]]
$jsonObj
)
begin {
$rootNode = 'root'
}
process {
foreach ($o in $jsonObj) {
$table = getNodes -job $o -path $rootNode
# $h = #{}
$a = #()
$pat = '^' + $rootNode
foreach ($i in $table) {
foreach ($k in $i.keys) {
# $h[$k -replace $pat, ''] = $i[$k]
$a += New-Object -TypeName psobject -Property #{'Key' = $($k -replace $pat, ''); 'Value' = $i[$k]}
# $h[$k -replace $pat, ''] = $i[$k]
}
}
# $h
$a
}
}
end{}
}
Example:
'{"name": "John","Address": {"house": "1234", "Street": "Boogie Ave"}, "pets": [{"Type": "Dog", "Age": 4, "Toys": ["rubberBall", "rope"]},{"Type": "Cat", "Age": 7, "Toys": ["catNip"]}]}' | ConvertFrom-Json | ConvertTo-JhcUtilJsonTable
Key Value
--- -----
.Address.house 1234
.Address.Street Boogie Ave
.name John
.pets[0].Age 4
.pets[0].Toys[0] rubberBall
.pets[0].Toys[1] rope
.pets[0].Type Dog
.pets[1].Age 7
.pets[1].Toys[0] catNip
.pets[1].Type Cat
I wanted an approach so that I could be able to easily convert my json data into a csv file.
The scenario is: I query data from somewhere and I receive an array of some model, like a bank extract.
This approach below is used to parse each one of these entries.
function jsonFlatter(data, previousKey, obj) {
obj = obj || {}
previousKey = previousKey || ""
Object.keys(data).map(key => {
let newKey = `${previousKey}${previousKey ? "_" : ""}${key}`
let _value = data[key]
let isArray = Array.isArray(_value)
if (typeof _value !== "object" || isArray || _value == null) {
if (isArray) {
_value = JSON.stringify(_value)
} else if (_value == null) {
_value = "null"
}
obj[newKey] = _value
} else if (typeof _value === "object") {
if (!Object.keys(_value).length) {
obj[newKey] = "null"
} else {
return jsonFlatter(_value, newKey, obj)
}
}
})
return obj
}
This way, I can count on the uniformity of the keys and inner keys of my object model, but arrays are simply stringified since I can't rely on their uniformity. Also, empty objects become the string "null", since I still want it's key to appear in the final result.
Usage example:
const test_data = {
a: {
aa: {
aaa: 4354,
aab: 654
},
ab: 123
},
b: 234,
c: {},
d: []
}
console.log('result', jsonFlatter(test_data))
#### output
{
"a_aa_aaa": 4354,
"a_aa_aab": 654,
"a_ab": 123,
"b": 234,
"c": "null",
"d": "[]"
}
try this one:
function getFlattenObject(data, response = {}) {
for (const key in data) {
if (typeof data[key] === 'object' && !Array.isArray(data[key])) {
getFlattenObject(data[key], response);
} else {
response[key] = data[key];
}
}
return response;
}
I'd like to add a new version of flatten case (this is what i needed :)) which, according to my probes with the above jsFiddler, is slightly faster then the currently selected one.
Moreover, me personally see this snippet a bit more readable, which is of course important for multi-developer projects.
function flattenObject(graph) {
let result = {},
item,
key;
function recurr(graph, path) {
if (Array.isArray(graph)) {
graph.forEach(function (itm, idx) {
key = path + '[' + idx + ']';
if (itm && typeof itm === 'object') {
recurr(itm, key);
} else {
result[key] = itm;
}
});
} else {
Reflect.ownKeys(graph).forEach(function (p) {
key = path + '.' + p;
item = graph[p];
if (item && typeof item === 'object') {
recurr(item, key);
} else {
result[key] = item;
}
});
}
}
recurr(graph, '');
return result;
}
Here is some code I wrote to flatten an object I was working with. It creates a new class that takes every nested field and brings it into the first layer. You could modify it to unflatten by remembering the original placement of the keys. It also assumes the keys are unique even across nested objects. Hope it helps.
class JSONFlattener {
ojson = {}
flattenedjson = {}
constructor(original_json) {
this.ojson = original_json
this.flattenedjson = {}
this.flatten()
}
flatten() {
Object.keys(this.ojson).forEach(function(key){
if (this.ojson[key] == null) {
} else if (this.ojson[key].constructor == ({}).constructor) {
this.combine(new JSONFlattener(this.ojson[key]).returnJSON())
} else {
this.flattenedjson[key] = this.ojson[key]
}
}, this)
}
combine(new_json) {
//assumes new_json is a flat array
Object.keys(new_json).forEach(function(key){
if (!this.flattenedjson.hasOwnProperty(key)) {
this.flattenedjson[key] = new_json[key]
} else {
console.log(key+" is a duplicate key")
}
}, this)
}
returnJSON() {
return this.flattenedjson
}
}
console.log(new JSONFlattener(dad_dictionary).returnJSON())
As an example, it converts
nested_json = {
"a": {
"b": {
"c": {
"d": {
"a": 0
}
}
}
},
"z": {
"b":1
},
"d": {
"c": {
"c": 2
}
}
}
into
{ a: 0, b: 1, c: 2 }
You can try out the package jpflat.
It flattens, inflates, resolves promises, flattens arrays, has customizable path creation and customizable value serialization.
The reducers and serializers receive the whole path as an array of it's parts, so more complex operations can be done to the path instead of modifying a single key or changing the delimiter.
Json path is the default, hence "jp"flat.
https://www.npmjs.com/package/jpflat
let flatFoo = await require('jpflat').flatten(foo)
I threw some code together to flatten and un-flatten complex/nested JavaScript objects. It works, but it's a bit slow (triggers the 'long script' warning).
For the flattened names I want "." as the delimiter and [INDEX] for arrays.
Examples:
un-flattened | flattened
---------------------------
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1].[0]":2,"[1].[1].[0]":3,"[1].[1].[1]":4,"[1].[2]":5,"[2]":6}
I created a benchmark that ~simulates my use case http://jsfiddle.net/WSzec/
Get a nested object
Flatten it
Look through it and possibly modify it while flattened
Unflatten it back to it's original nested format to be shipped away
I would like faster code: For clarification, code that completes the JSFiddle benchmark (http://jsfiddle.net/WSzec/) significantly faster (~20%+ would be nice) in IE 9+, FF 24+, and Chrome 29+.
Here's the relevant JavaScript code: Current Fastest: http://jsfiddle.net/WSzec/6/
var unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp;
for(var p in data) {
cur = result, prop = "", last = 0;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
prop = temp;
last = idx + 1;
} while(idx >= 0);
cur[prop] = data[p];
}
return result[""];
}
var flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop ? prop+"."+i : ""+i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
EDIT 1 Modified the above to #Bergi 's implementation which is currently the fastest. As an aside, using ".indexOf" instead of "regex.exec" is around 20% faster in FF but 20% slower in Chrome; so I'll stick with the regex since it's simpler (here's my attempt at using indexOf to replace the regex http://jsfiddle.net/WSzec/2/).
EDIT 2 Building on #Bergi 's idea I managed to created a faster non-regex version (3x faster in FF and ~10% faster in Chrome). http://jsfiddle.net/WSzec/6/ In the this (the current) implementation the rules for key names are simply, keys cannot start with an integer or contain a period.
Example:
{"foo":{"bar":[0]}} => {"foo.bar.0":0}
EDIT 3 Adding #AaditMShah 's inline path parsing approach (rather than String.split) helped to improve the unflatten performance. I'm very happy with the overall performance improvement reached.
The latest jsfiddle and jsperf:
http://jsfiddle.net/WSzec/14/
http://jsperf.com/flatten-un-flatten/4
Here's my much shorter implementation:
Object.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
resultholder = {};
for (var p in data) {
var cur = resultholder,
prop = "",
m;
while (m = regex.exec(p)) {
cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
prop = m[2] || m[1];
}
cur[prop] = data[p];
}
return resultholder[""] || resultholder;
};
flatten hasn't changed much (and I'm not sure whether you really need those isEmpty cases):
Object.flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
Together, they run your benchmark in about the half of the time (Opera 12.16: ~900ms instead of ~ 1900ms, Chrome 29: ~800ms instead of ~1600ms).
Note: This and most other solutions answered here focus on speed and are susceptible to prototype pollution and shold not be used on untrusted objects.
I wrote two functions to flatten and unflatten a JSON object.
Flatten a JSON object:
var flatten = (function (isArray, wrapped) {
return function (table) {
return reduce("", {}, table);
};
function reduce(path, accumulator, table) {
if (isArray(table)) {
var length = table.length;
if (length) {
var index = 0;
while (index < length) {
var property = path + "[" + index + "]", item = table[index++];
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else accumulator[path] = table;
} else {
var empty = true;
if (path) {
for (var property in table) {
var item = table[property], property = path + "." + property, empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else {
for (var property in table) {
var item = table[property], empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
}
if (empty) accumulator[path] = table;
}
return accumulator;
}
}(Array.isArray, Object));
Performance:
It's faster than the current solution in Opera. The current solution is 26% slower in Opera.
It's faster than the current solution in Firefox. The current solution is 9% slower in Firefox.
It's faster than the current solution in Chrome. The current solution is 29% slower in Chrome.
Unflatten a JSON object:
function unflatten(table) {
var result = {};
for (var path in table) {
var cursor = result, length = path.length, property = "", index = 0;
while (index < length) {
var char = path.charAt(index);
if (char === "[") {
var start = index + 1,
end = path.indexOf("]", start),
cursor = cursor[property] = cursor[property] || [],
property = path.slice(start, end),
index = end + 1;
} else {
var cursor = cursor[property] = cursor[property] || {},
start = char === "." ? index + 1 : index,
bracket = path.indexOf("[", start),
dot = path.indexOf(".", start);
if (bracket < 0 && dot < 0) var end = index = length;
else if (bracket < 0) var end = index = dot;
else if (dot < 0) var end = index = bracket;
else var end = index = bracket < dot ? bracket : dot;
var property = path.slice(start, end);
}
}
cursor[property] = table[path];
}
return result[""];
}
Performance:
It's faster than the current solution in Opera. The current solution is 5% slower in Opera.
It's slower than the current solution in Firefox. My solution is 26% slower in Firefox.
It's slower than the current solution in Chrome. My solution is 6% slower in Chrome.
Flatten and unflatten a JSON object:
Overall my solution performs either equally well or even better than the current solution.
Performance:
It's faster than the current solution in Opera. The current solution is 21% slower in Opera.
It's as fast as the current solution in Firefox.
It's faster than the current solution in Firefox. The current solution is 20% slower in Chrome.
Output format:
A flattened object uses the dot notation for object properties and the bracket notation for array indices:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}
In my opinion this format is better than only using the dot notation:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a.0.b.0":"c","a.0.b.1":"d"}
[1,[2,[3,4],5],6] => {"0":1,"1.0":2,"1.1.0":3,"1.1.1":4,"1.2":5,"2":6}
Advantages:
Flattening an object is faster than the current solution.
Flattening and unflattening an object is as fast as or faster than the current solution.
Flattened objects use both the dot notation and the bracket notation for readability.
Disadvantages:
Unflattening an object is slower than the current solution in most (but not all) cases.
The current JSFiddle demo gave the following values as output:
Nested : 132175 : 63
Flattened : 132175 : 564
Nested : 132175 : 54
Flattened : 132175 : 508
My updated JSFiddle demo gave the following values as output:
Nested : 132175 : 59
Flattened : 132175 : 514
Nested : 132175 : 60
Flattened : 132175 : 451
I'm not really sure what that means, so I'll stick with the jsPerf results. After all jsPerf is a performance benchmarking utility. JSFiddle is not.
ES6 version:
const flatten = (obj, path = '') => {
if (!(obj instanceof Object)) return {[path.replace(/\.$/g, '')]:obj};
return Object.keys(obj).reduce((output, key) => {
return obj instanceof Array ?
{...output, ...flatten(obj[key], path + '[' + key + '].')}:
{...output, ...flatten(obj[key], path + key + '.')};
}, {});
}
Example:
console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
3 ½ Years later...
For my own project I wanted to flatten JSON objects in mongoDB dot notation and came up with a simple solution:
/**
* Recursively flattens a JSON object using dot notation.
*
* NOTE: input must be an object as described by JSON spec. Arbitrary
* JS objects (e.g. {a: () => 42}) may result in unexpected output.
* MOREOVER, it removes keys with empty objects/arrays as value (see
* examples bellow).
*
* #example
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e': 3, 'b.1': 4}
* flatten({a: 1, b: [{c: 2, d: {e: 3}}, 4]})
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e.0': true, 'b.0.d.e.1': false, 'b.0.d.e.2.f': 1}
* flatten({a: 1, b: [{c: 2, d: {e: [true, false, {f: 1}]}}]})
* // return {a: 1}
* flatten({a: 1, b: [], c: {}})
*
* #param obj item to be flattened
* #param {Array.string} [prefix=[]] chain of prefix joined with a dot and prepended to key
* #param {Object} [current={}] result of flatten during the recursion
*
* #see https://docs.mongodb.com/manual/core/document/#dot-notation
*/
function flatten (obj, prefix, current) {
prefix = prefix || []
current = current || {}
// Remember kids, null is also an object!
if (typeof (obj) === 'object' && obj !== null) {
Object.keys(obj).forEach(key => {
this.flatten(obj[key], prefix.concat(key), current)
})
} else {
current[prefix.join('.')] = obj
}
return current
}
Features and/or caveats
It only accepts JSON objects. So if you pass something like {a: () => {}} you might not get what you wanted!
It removes empty arrays and objects. So this {a: {}, b: []} is flattened to {}.
Use this library:
npm install flat
Usage (from https://www.npmjs.com/package/flat):
Flatten:
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
Un-flatten:
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
Here's another approach that runs slower (about 1000ms) than the above answer, but has an interesting idea :-)
Instead of iterating through each property chain, it just picks the last property and uses a look-up-table for the rest to store the intermediate results. This look-up-table will be iterated until there are no property chains left and all values reside on uncocatenated properties.
JSON.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)$|\[(\d+)\]$/,
props = Object.keys(data),
result, p;
while(p = props.shift()) {
var m = regex.exec(p),
target;
if (m.index) {
var rest = p.slice(0, m.index);
if (!(rest in data)) {
data[rest] = m[2] ? [] : {};
props.push(rest);
}
target = data[rest];
} else {
target = result || (result = (m[2] ? [] : {}));
}
target[m[2] || m[1]] = data[p];
}
return result;
};
It currently uses the data input parameter for the table, and puts lots of properties on it - a non-destructive version should be possible as well. Maybe a clever lastIndexOf usage performs better than the regex (depends on the regex engine).
See it in action here.
You can use https://github.com/hughsk/flat
Take a nested Javascript object and flatten it, or unflatten an object with delimited keys.
Example from the doc
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
This code recursively flattens out JSON objects.
I included my timing mechanism in the code and it gives me 1ms but I'm not sure if that's the most accurate one.
var new_json = [{
"name": "fatima",
"age": 25,
"neighbour": {
"name": "taqi",
"location": "end of the street",
"property": {
"built in": 1990,
"owned": false,
"years on market": [1990, 1998, 2002, 2013],
"year short listed": [], //means never
}
},
"town": "Mountain View",
"state": "CA"
},
{
"name": "qianru",
"age": 20,
"neighbour": {
"name": "joe",
"location": "opposite to the park",
"property": {
"built in": 2011,
"owned": true,
"years on market": [1996, 2011],
"year short listed": [], //means never
}
},
"town": "Pittsburgh",
"state": "PA"
}]
function flatten(json, flattened, str_key) {
for (var key in json) {
if (json.hasOwnProperty(key)) {
if (json[key] instanceof Object && json[key] != "") {
flatten(json[key], flattened, str_key + "." + key);
} else {
flattened[str_key + "." + key] = json[key];
}
}
}
}
var flattened = {};
console.time('flatten');
flatten(new_json, flattened, "");
console.timeEnd('flatten');
for (var key in flattened){
console.log(key + ": " + flattened[key]);
}
Output:
flatten: 1ms
.0.name: fatima
.0.age: 25
.0.neighbour.name: taqi
.0.neighbour.location: end of the street
.0.neighbour.property.built in: 1990
.0.neighbour.property.owned: false
.0.neighbour.property.years on market.0: 1990
.0.neighbour.property.years on market.1: 1998
.0.neighbour.property.years on market.2: 2002
.0.neighbour.property.years on market.3: 2013
.0.neighbour.property.year short listed:
.0.town: Mountain View
.0.state: CA
.1.name: qianru
.1.age: 20
.1.neighbour.name: joe
.1.neighbour.location: opposite to the park
.1.neighbour.property.built in: 2011
.1.neighbour.property.owned: true
.1.neighbour.property.years on market.0: 1996
.1.neighbour.property.years on market.1: 2011
.1.neighbour.property.year short listed:
.1.town: Pittsburgh
.1.state: PA
Here's mine. It runs in <2ms in Google Apps Script on a sizable object. It uses dashes instead of dots for separators, and it doesn't handle arrays specially like in the asker's question, but this is what I wanted for my use.
function flatten (obj) {
var newObj = {};
for (var key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
var temp = flatten(obj[key])
for (var key2 in temp) {
newObj[key+"-"+key2] = temp[key2];
}
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
Example:
var test = {
a: 1,
b: 2,
c: {
c1: 3.1,
c2: 3.2
},
d: 4,
e: {
e1: 5.1,
e2: 5.2,
e3: {
e3a: 5.31,
e3b: 5.32
},
e4: 5.4
},
f: 6
}
Logger.log("start");
Logger.log(JSON.stringify(flatten(test),null,2));
Logger.log("done");
Example output:
[17-02-08 13:21:05:245 CST] start
[17-02-08 13:21:05:246 CST] {
"a": 1,
"b": 2,
"c-c1": 3.1,
"c-c2": 3.2,
"d": 4,
"e-e1": 5.1,
"e-e2": 5.2,
"e-e3-e3a": 5.31,
"e-e3-e3b": 5.32,
"e-e4": 5.4,
"f": 6
}
[17-02-08 13:21:05:247 CST] done
Object.prototype.flatten = function (obj) {
let ans = {};
let anotherObj = { ...obj };
function performFlatten(anotherObj) {
Object.keys(anotherObj).forEach((key, idx) => {
if (typeof anotherObj[key] !== 'object') {
ans[key] = anotherObj[key];
console.log('ans so far : ', ans);
} else {
console.log(key, { ...anotherObj[key] });
performFlatten(anotherObj[key]);
}
})
}
performFlatten(anotherObj);
return ans;
}
let ans = flatten(obj);
console.log(ans);
I added +/- 10-15% efficiency to the selected answer by minor code refactoring and moving the recursive function outside of the function namespace.
See my question: Are namespaced functions reevaluated on every call? for why this slows nested functions down.
function _flatten (target, obj, path) {
var i, empty;
if (obj.constructor === Object) {
empty = true;
for (i in obj) {
empty = false;
_flatten(target, obj[i], path ? path + '.' + i : i);
}
if (empty && path) {
target[path] = {};
}
}
else if (obj.constructor === Array) {
i = obj.length;
if (i > 0) {
while (i--) {
_flatten(target, obj[i], path + '[' + i + ']');
}
} else {
target[path] = [];
}
}
else {
target[path] = obj;
}
}
function flatten (data) {
var result = {};
_flatten(result, data, null);
return result;
}
See benchmark.
Here's a recursive solution for flatten I put together in PowerShell:
#---helper function for ConvertTo-JhcUtilJsonTable
#
function getNodes {
param (
[Parameter(Mandatory)]
[System.Object]
$job,
[Parameter(Mandatory)]
[System.String]
$path
)
$t = $job.GetType()
$ct = 0
$h = #{}
if ($t.Name -eq 'PSCustomObject') {
foreach ($m in Get-Member -InputObject $job -MemberType NoteProperty) {
getNodes -job $job.($m.Name) -path ($path + '.' + $m.Name)
}
}
elseif ($t.Name -eq 'Object[]') {
foreach ($o in $job) {
getNodes -job $o -path ($path + "[$ct]")
$ct++
}
}
else {
$h[$path] = $job
$h
}
}
#---flattens a JSON document object into a key value table where keys are proper JSON paths corresponding to their value
#
function ConvertTo-JhcUtilJsonTable {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Object[]]
$jsonObj
)
begin {
$rootNode = 'root'
}
process {
foreach ($o in $jsonObj) {
$table = getNodes -job $o -path $rootNode
# $h = #{}
$a = #()
$pat = '^' + $rootNode
foreach ($i in $table) {
foreach ($k in $i.keys) {
# $h[$k -replace $pat, ''] = $i[$k]
$a += New-Object -TypeName psobject -Property #{'Key' = $($k -replace $pat, ''); 'Value' = $i[$k]}
# $h[$k -replace $pat, ''] = $i[$k]
}
}
# $h
$a
}
}
end{}
}
Example:
'{"name": "John","Address": {"house": "1234", "Street": "Boogie Ave"}, "pets": [{"Type": "Dog", "Age": 4, "Toys": ["rubberBall", "rope"]},{"Type": "Cat", "Age": 7, "Toys": ["catNip"]}]}' | ConvertFrom-Json | ConvertTo-JhcUtilJsonTable
Key Value
--- -----
.Address.house 1234
.Address.Street Boogie Ave
.name John
.pets[0].Age 4
.pets[0].Toys[0] rubberBall
.pets[0].Toys[1] rope
.pets[0].Type Dog
.pets[1].Age 7
.pets[1].Toys[0] catNip
.pets[1].Type Cat
I wanted an approach so that I could be able to easily convert my json data into a csv file.
The scenario is: I query data from somewhere and I receive an array of some model, like a bank extract.
This approach below is used to parse each one of these entries.
function jsonFlatter(data, previousKey, obj) {
obj = obj || {}
previousKey = previousKey || ""
Object.keys(data).map(key => {
let newKey = `${previousKey}${previousKey ? "_" : ""}${key}`
let _value = data[key]
let isArray = Array.isArray(_value)
if (typeof _value !== "object" || isArray || _value == null) {
if (isArray) {
_value = JSON.stringify(_value)
} else if (_value == null) {
_value = "null"
}
obj[newKey] = _value
} else if (typeof _value === "object") {
if (!Object.keys(_value).length) {
obj[newKey] = "null"
} else {
return jsonFlatter(_value, newKey, obj)
}
}
})
return obj
}
This way, I can count on the uniformity of the keys and inner keys of my object model, but arrays are simply stringified since I can't rely on their uniformity. Also, empty objects become the string "null", since I still want it's key to appear in the final result.
Usage example:
const test_data = {
a: {
aa: {
aaa: 4354,
aab: 654
},
ab: 123
},
b: 234,
c: {},
d: []
}
console.log('result', jsonFlatter(test_data))
#### output
{
"a_aa_aaa": 4354,
"a_aa_aab": 654,
"a_ab": 123,
"b": 234,
"c": "null",
"d": "[]"
}
try this one:
function getFlattenObject(data, response = {}) {
for (const key in data) {
if (typeof data[key] === 'object' && !Array.isArray(data[key])) {
getFlattenObject(data[key], response);
} else {
response[key] = data[key];
}
}
return response;
}
I'd like to add a new version of flatten case (this is what i needed :)) which, according to my probes with the above jsFiddler, is slightly faster then the currently selected one.
Moreover, me personally see this snippet a bit more readable, which is of course important for multi-developer projects.
function flattenObject(graph) {
let result = {},
item,
key;
function recurr(graph, path) {
if (Array.isArray(graph)) {
graph.forEach(function (itm, idx) {
key = path + '[' + idx + ']';
if (itm && typeof itm === 'object') {
recurr(itm, key);
} else {
result[key] = itm;
}
});
} else {
Reflect.ownKeys(graph).forEach(function (p) {
key = path + '.' + p;
item = graph[p];
if (item && typeof item === 'object') {
recurr(item, key);
} else {
result[key] = item;
}
});
}
}
recurr(graph, '');
return result;
}
Here is some code I wrote to flatten an object I was working with. It creates a new class that takes every nested field and brings it into the first layer. You could modify it to unflatten by remembering the original placement of the keys. It also assumes the keys are unique even across nested objects. Hope it helps.
class JSONFlattener {
ojson = {}
flattenedjson = {}
constructor(original_json) {
this.ojson = original_json
this.flattenedjson = {}
this.flatten()
}
flatten() {
Object.keys(this.ojson).forEach(function(key){
if (this.ojson[key] == null) {
} else if (this.ojson[key].constructor == ({}).constructor) {
this.combine(new JSONFlattener(this.ojson[key]).returnJSON())
} else {
this.flattenedjson[key] = this.ojson[key]
}
}, this)
}
combine(new_json) {
//assumes new_json is a flat array
Object.keys(new_json).forEach(function(key){
if (!this.flattenedjson.hasOwnProperty(key)) {
this.flattenedjson[key] = new_json[key]
} else {
console.log(key+" is a duplicate key")
}
}, this)
}
returnJSON() {
return this.flattenedjson
}
}
console.log(new JSONFlattener(dad_dictionary).returnJSON())
As an example, it converts
nested_json = {
"a": {
"b": {
"c": {
"d": {
"a": 0
}
}
}
},
"z": {
"b":1
},
"d": {
"c": {
"c": 2
}
}
}
into
{ a: 0, b: 1, c: 2 }
You can try out the package jpflat.
It flattens, inflates, resolves promises, flattens arrays, has customizable path creation and customizable value serialization.
The reducers and serializers receive the whole path as an array of it's parts, so more complex operations can be done to the path instead of modifying a single key or changing the delimiter.
Json path is the default, hence "jp"flat.
https://www.npmjs.com/package/jpflat
let flatFoo = await require('jpflat').flatten(foo)
I have an object:
{"f":{"cid":"325","field_name[10][0]":"133","field_name[10][1]":"132","price":"320|3600"}}
And I would like to convert this object to query string.
I'm using this function:
function toQueryString(obj, prefix) {
var str = [];
for(var p in obj) {
var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
str.push(typeof v == "object" ?
toQueryString(v, k) :
encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
return str.join("&");
}
But this function gives me result:
f[cid]=325&f[field_name[10][0]]=133&f[field_name[10][1]]=132&f[price]=320%7C3600
This is wrong as I can't get right result on my server side:
Array
(
[f] => Array
(
[cid] => 325
[field_name[10] => Array
(
[0] => 133
)
[price] => 320|3600
)
)
How can I solve this problem?
I think the right result will be something like this:
f[cid]=325&f[field_name[[10][0]]]=133&f[field_name[[10][1]]]=132&f[price]=320%7C3600
I changed your function a little in order to correct the nested query strings:
function toQueryString(obj, prefix) {
var str = [], k, v;
for(var p in obj) {
if (!obj.hasOwnProperty(p)) {continue;} // skip things from the prototype
if (~p.indexOf('[')) {
k = prefix ? prefix + "[" + p.substring(0, p.indexOf('[')) + "]" + p.substring(p.indexOf('[')) : p;
// only put whatever is before the bracket into new brackets; append the rest
} else {
k = prefix ? prefix + "[" + p + "]" : p;
}
v = obj[p];
str.push(typeof v == "object" ?
toQueryString(v, k) :
encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
return str.join("&");
}
Running this function on your original object now gives us this query string:
f[cid]=325&f[field_name][10][0]=133&f[field_name][10][1]=132&f[price]=320|3600
If we pass this to a PHP page told to print_r($_GET), it gives us:
Array
(
[f] => Array
(
[cid] => 325
[field_name] => Array
(
[10] => Array
(
[0] => 133
[1] => 132
)
)
[price] => 320|3600
)
)
Exactly what you wanted, right?
This question already has an answer that does what you want, but I decided to explain the alternative that I hinted at in my comment because I think it is good to know more than one way of doing things.
It doesn't do what you want exactly, but I think it is a simpler, robuster, and more maintainable method of transmitting arbitrary objects.
function toQueryString(obj, name) {
return encodeURIComponent(name) + '=' +
encodeURIComponent(JSON.stringify(obj));
}
On the php side (assuming name was "foo"), all you have to do is:
$foo=json_decode($_GET["foo"], true);
The only bit of difficulty is that if you want to support certain versions of Internet Explorer, you have to use a polyfill for JSON.stringify, but these are readily available.
The drawback is naturally an extra indirection, but I think there is a major benefit: you are pushing the burden of bug testing this to the browser manufacturers and (if necessary) the developers of whatever implementation of JSON.stringify you decide to use.
Granted that has the drawback of introducing a new dependency, but I believe it is useful enough to warrant it.
Your mileage may vary.
Also: if you can send the data using a POST request, than you can directly send the result of JSON.stringify as raw POST data without URL encoding it. In the PHP side you can fetch it with json_decode(file_get_contents("php://input"), true)
I just wrote this. It works for me.
let objToQueryString = function (obj, prefix) {
let fields = [];
if (obj && Object.keys(obj).length) {
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
if (typeof obj[i] === 'object') {
fields.push(objToQueryString(obj[i], prefix ? prefix + '[' + i + ']' : i));
} else {
fields.push((prefix ? prefix + '[' + i + ']' : i) + '=' + (typeof obj[i] === 'undefined' ? '' : encodeURIComponent(obj[i])));
}
}
}
} else if (prefix) {
fields.push(prefix + '=');
}
return fields.join('&');
};
objToQueryString({x: 1, y: 2, z: {a: 10, b: 20, c: {i: 100, j: null, k: {}}}}); //returns "x=1&y=2&z[a]=10&z[b]=20&z[c][i]=100&z[c][j]=&z[c][k]="
Enjoy.
TypeScript
declare global {
interface Object {
queryString: () => string;
}
}
if (!Object.prototype.queryString) {
Object.defineProperty(Object.prototype, 'queryString', {
value: function () {
var parts = [];
let obj = this;
for (var key in obj) {
if (typeof obj[key] == 'object') {
for (var key2 in obj[key]) {
parts.push(`${encodeURIComponent(key)}[${encodeURIComponent(key2)}]` + '=' + encodeURIComponent(obj[key][key2]));
}
} else if (typeof obj[key] == 'string') {
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]));
}
}
return "?" + parts.join('&');
}
});
}
input: {"test":{"convert":"querystring"}}
output: test[convert]=querystring
function solution(inputString, objectName)
{
var startingIndex = inputString.replace(/'/g, "").indexOf('{')
var endingIndex = inputString.replace(/'/g, "").indexOf('}')
var propertyValuePairs = inputString.replace(/'/g, "").substring(startingIndex + 1, endingIndex ).split(',')
var propertyValues = new Array();
$.each(propertyValuePairs , function(index, element){
var elements = element.split(':'); propertyValues.push({property: elements[0], value: elements[1]});
});
var result = "";
for (var i = 0; i < propertyValues.length; i++) {
result += objectName + "[" + propertyValues[i].property + "]" + "=" + propertyValues[i].value + "&";
}
return result.substring(0, result.length - 1);
}