Related
I'm trying to build a function that would expand an object like :
{
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
}
Into a nested object :
{ab: {cd: {e:'foo', f:'bar'}, g:'foo2'}}
Like this php function : Set::expand()
Without using eval of course.
I believe this is what you're after:
function deepen(obj) {
const result = {};
// For each object path (property key) in the object
for (const objectPath in obj) {
// Split path into component parts
const parts = objectPath.split('.');
// Create sub-objects along path as needed
let target = result;
while (parts.length > 1) {
const part = parts.shift();
target = target[part] = target[part] || {};
}
// Set value at end of path
target[parts[0]] = obj[objectPath]
}
return result;
}
// For example ...
console.log(deepen({
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
}));
If you're using Node.js (e.g. - if not cut and paste out of our module), try this package: https://www.npmjs.org/package/dataobject-parser
Built a module that does the forward/reverse operations:
https://github.com/Gigzolo/dataobject-parser
It's designed as a self managed object right now. Used by instantiating an instance of DataObjectParser.
var structured = DataObjectParser.transpose({
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
});
structured.data() returns your nested object:
{ab: {cd: {e:'foo', f:'bar'}, g:'foo2'}}
So here's a working example in JSFiddle:
http://jsfiddle.net/H8Cqx/
Function name is terrible and the code was quickly made, but it should work. Note that this modifies the original object, I am not sure if you wanted to create a new object that is expanded version of the old one.
(function(){
function parseDotNotation( str, val, obj ){
var currentObj = obj,
keys = str.split("."), i, l = keys.length - 1, key;
for( i = 0; i < l; ++i ) {
key = keys[i];
currentObj[key] = currentObj[key] || {};
currentObj = currentObj[key];
}
currentObj[keys[i]] = val;
delete obj[str];
}
Object.expand = function( obj ) {
for( var key in obj ) {
parseDotNotation( key, obj[key], obj );
}
return obj;
};
})();
var expanded = Object.expand({
'ab.cd.e' : 'foo',
'ab.cd.f' : 'bar',
'ab.g' : 'foo2'
});
JSON.stringify( expanded );
//"{"ab":{"cd":{"e":"foo","f":"bar"},"g":"foo2"}}"
Derived from Esailija's answer, with fixes to support multiple top-level keys.
(function () {
function parseDotNotation(str, val, obj) {
var currentObj = obj,
keys = str.split("."),
i, l = Math.max(1, keys.length - 1),
key;
for (i = 0; i < l; ++i) {
key = keys[i];
currentObj[key] = currentObj[key] || {};
currentObj = currentObj[key];
}
currentObj[keys[i]] = val;
delete obj[str];
}
Object.expand = function (obj) {
for (var key in obj) {
if (key.indexOf(".") !== -1)
{
parseDotNotation(key, obj[key], obj);
}
}
return obj;
};
})();
var obj = {
"pizza": "that",
"this.other": "that",
"alphabets": [1, 2, 3, 4],
"this.thing.that": "this"
}
Outputs:
{
"pizza": "that",
"alphabets": [
1,
2,
3,
4
],
"this": {
"other": "that",
"thing": {
"that": "this"
}
}
}
Fiddle
You could split the key string as path and reduce it for assigning the value by using a default object for unvisited levels.
function setValue(object, path, value) {
var keys = path.split('.'),
last = keys.pop();
keys.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
return object;
}
var source = { 'ab.cd.e': 'foo', 'ab.cd.f': 'bar', 'ab.g': 'foo2' },
target = Object
.entries(source)
.reduce((o, [k, v]) => setValue(o, k, v), {});
console.log(target);
You need to convert each string key into object. Using following function you can get desire result.
function convertIntoJSON(obj) {
var o = {}, j, d;
for (var m in obj) {
d = m.split(".");
var startOfObj = o;
for (j = 0; j < d.length ; j += 1) {
if (j == d.length - 1) {
startOfObj[d[j]] = obj[m];
}
else {
startOfObj[d[j]] = startOfObj[d[j]] || {};
startOfObj = startOfObj[d[j]];
}
}
}
return o;
}
Now call this function
var aa = {
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
};
var desiredObj = convertIntoJSON(aa);
Something that works, but is probably not the most efficient way to do so (also relies on ECMA 5 Object.keys() method, but that can be easily replaced.
var input = {
'ab.cd.e': 'foo',
'ab.cd.f': 'bar',
'ab.g': 'foo2'
};
function createObjects(parent, chainArray, value) {
if (chainArray.length == 1) {
parent[chainArray[0]] = value;
return parent;
}
else {
parent[chainArray[0]] = parent[chainArray[0]] || {};
return createObjects(parent[chainArray[0]], chainArray.slice(1, chainArray.length), value);
}
}
var keys = Object.keys(input);
var result = {};
for(var i = 0, l = keys.length; i < l; i++)
{
createObjects(result, keys[i].split('.'), input[keys[i]]);
}
JSFiddle is here.
Here is how I do this in one of my applications:
const obj = {
"start.headline": "1 headline",
"start.subHeadline": "subHeadline",
"start.accordion.headline": "2 headline",
"start.accordion.sections.0.content": "content 0",
"start.accordion.sections.0.iconName": "icon 0",
"start.accordion.sections.1.headline": "headline 1",
"start.accordion.sections.1.content": "content 1",
"start.accordion.sections.1.iconName": "icon 1",
"start.accordion.sections.2.headline": "headline 2",
"start.accordion.sections.2.content": "content 2",
"start.accordion.sections.2.iconName": "icon 2",
"end.field": "add headline",
"end.button": "add button",
"end.msgs.success": "success msg",
"end.msgs.error": "error msg",
};
const res = Object.keys(obj).reduce((res, key) => {
const path = key.split('.');
const lastIndex = path.length - 1;
path.reduce(
(acc, k, i, a) => acc[k] = lastIndex === i ?
obj[key] :
acc[k] || (/\d/.test(a[i+1]) ? [] : {}),
res
);
return res;
}, {});
console.log(res);
This is the answer as provided by #broofa, but converted to TypeScript.
type NestedObject = { [key: string]: any };
function objectify(obj: NestedObject): NestedObject {
const result: NestedObject = {};
for (const key in obj) {
let target: NestedObject = result;
const parts = key.split(".");
for (let j = 0; j < parts.length - 1; j++) {
const part = parts[j];
target = target[part] = target[part] || {};
}
target[parts[parts.length - 1]] = obj[key];
}
return result;
}
There is a string imitating a CSV file, you have to pass it into a function and get an array consisting of the objects. How to do it? Please help.
function STRtoArray (str) {
// Code here
}
var str = 'Name,Age,Car,wife \n John,25,,true\n Ben,31,wolksvagen,false'
The result is expected as follows:
[
{
Name: John,
Age: 25,
Car: false,
wife: true
},
{
Name: Kolya,
Age: 31,
Car: wolksvagen,
wife: false
}
]
function strToArray (str) {
var lines = str.split(/\n/);
var keys = lines[0].split(',');
var arr = [];
for (var i = 1; i < lines.length; i++) {
var line = lines[i].trim().split(',');
var obj = {};
for (var j = 0; j < line.length; j++) {
obj[keys[j].trim()] = (line[j].length
? (line[j] === 'true'
? true
: (line[j] === 'false'
? false
: (isNaN(line[j])
? line[j]
: parseInt(line[j]))))
: false);
}
arr.push(obj);
}
return arr;
}
var str = 'Name,Age,Car,wife \n John,25,,true\n Ben,31,wolksvagen,false'
console.log(strToArray(str));
The solution using String.prototype.split(), Array.prototype.map() and Array.prototype.reduce()functions:
function STRtoArray(str) {
var lines = str.split('\n'),
keys = lines[0].trim().split(','); // getting key fields
var result = lines.slice(1).map(function (l){
return l.trim().split(',').reduce(function (r, l, i) {
if (i === 1) l = Number(l); // casting 'Age' field to number type
if (i === 3) l = Boolean(l === 'true'? 1:0);
r[keys[i]] = l;
return r;
}, {});
});
return result;
}
var str = 'Name,Age,Car,wife \n John,25,,true\n Ben,31,wolksvagen,false'
console.log(STRtoArray(str));
Given data such as :
var people = [
{ 'myKey': 'John Kenedy', 'status': 1 },
{ 'myKey': 'Steeven Red', 'status': 0 },
{ 'myKey': 'Mary_Kenedy', 'status': 3 },
{ 'myKey': 'Carl Orange', 'status': 0 },
{ 'myKey': 'Lady Purple', 'status': 0 },
... // thousands more
];
How to efficiently get the list of all objects which contains in myKey the string Kenedy ?
http://jsfiddle.net/yb3rdhm8/
Note: I currently use str.search() :
The search("str") returns the position of the match. Returns -1 if no match is found.
to do as follow :
var map_partial_matches = function(object, str){
var list_of_people_with_kenedy = [] ;
for (var j in object) {
if (object[j]["myKey"].search(str) != -1) {
object[j].presidentName = "yes"; // do something to object[j]
list_of_people_with_kenedy.push({ "people": object[j]["myKey"] }); // add object key to new list
}
} return list_of_people_with_kenedy;
}
map_partial_matches(people, "Kenedy");
I could do the same using str.match() :
str.match() returns the matches, as an Array object. Returns null if no match is found.
It works anyway, but I have no idea if it's efficient or completely dump.
You can use filter():
var filtered = people.filter(function (item) {
if (item.myKey.indexOf("Kenedy") != -1)
return item;
});
You can also checkout Sugar.js
In order to search your unsorted object you need to get through all of it's properties - So I'd say a simple loop with an indexOf will be pretty much the best you can go:
var foundItems = [];
for(var i = 0; i < people.length ;i++)
{
if(people[i].myKey.indexOf('Kenedy') > -1)
foundItems.push(people[i]]);
}
Maybe you can tweak it up a little, but it's pretty much the best you can get.
You can write a basic function that uses filter to return an array of matches based on a key and value:
function find(arr, key, val) {
return arr.filter(function (el) {
return el[key].indexOf(val) > -1;
});
}
var result = find(people, 'myKey', 'Kenedy');
Alternatively use a normal for...loop:
function find(arr, key, val) {
var out = [];
for (var i = 0, l = arr.length; i < l; i++) {
if (arr[i][key].indexOf(val) > -1) {
out.push(arr[i]);
}
}
return out;
}
DEMO
Does the Object Contain a Given Key?
function hKey(obj, key) {
arr = [];
// newarr =[];
for(el in obj){
arr.push(el)
} //return arr;
for(i=0; i<arr.length; i++){
name = arr[i]
} if(name == key) {
return true;
}else {
return false;
}
}
console.log(hKey({ a: 44, b: 45, c: 46 }, "c"))
I have an application with nodejs express who makes request but when I pass an array I am:
Example: /foo?id=1&id=3&id=5
How to remove '[]' ?
var requestQueryParams = {id: [1,3,5]}
var options = {
url: 'www.test.com',
headers: {'content-type': 'application/json', 'accept': 'application/json'},
qs: requestQueryParams || {}
};
request.get(options), function(){...}
result: www.test.com?id[0]=1&id[1]=3&id[2]=5
Request.js = https://www.npmjs.org/package/request
Qs.js = https://www.npmjs.org/package/qs
Qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'
Since some time now there is a better solution. request uses qs per default to stringify the passed in qs object which also accepts options for formatting it. One of this options is arrayFormat which accepts the following values:
indices: Default behavior. Produces a string including the index: id[0]=foo&id[1]=bar&id[2]=baz. Useful if you want to ensure the correct order.
brackets: Produces a string with only brackets appended: id[]=foo&id[]=bar&id[]=baz.
repeat: Produces a string without any brackets: id=foo&id=bar&id=baz. Older services that don't support arrays may accept this but will then only use the last value.
A options object with this would look like the following:
const request = require('request');
const options = {
...
qs: {
id: [ 'foo', 'bar', 'baz' ],
},
qsStringifyOptions: { arrayFormat: 'repeat' },
};
request(options);
See also https://github.com/request/request#requestoptions-callback, where that option is also mentioned.
What do you want instead? If you want www.test.com?id0=1&id1=3&id2=5, then you need to give it a params object like this:
var requestQueryParams = { id0: 1, id1: 3, id2: 5 }
If you already have an object that looks like { id: [1,2,3] }, then you need to convert that object into one like the above. You can do that in a for loop easily enough:
var requestQueryParams = { id: [1,3,5] },
newRequestQueryParams = {};
for(var i = 0; i < requestQueryParams.id.length; i++) {
var paramName = "id" + i, // "id0", "id1", etc.
id = requestQueryParams.id[i];
newRequestQueryParams[paramName] = id;
}
console.log(newRequestQueryParams);
// => { id0: 1, id1: 3, id2: 5 }
Update: If you want a query string like id=1&id=3&id=5 (although this would be very strange, as I mention in my comment below), you can also do it in a for loop, as above, or you could do something like this:
var requestQueryParams = { id: [1,3,5] },
queryStringParts = [], // an array this time
queryString;
for(var i = 0; i < requestQueryParams.id.length; i++) {
var param = "id=" + parseInt( requestQueryParams.id[i] );
queryStringParts.push(param);
}
// queryStringParts is now [ "id=1", "id=3", "id=5" ]
queryString = queryStringParts.join("&")
console.log(queryString);
// => "id=1&id=3&id=5"
I used parseInt inside the for loop because I'm assuming the IDs are coming from an untrusted source (e.g. a user) and, since you're building a string manually instead of using a library that will encode the data for you, you want to prevent a malicious user from injecting arbitrary strings into your request. You could also use encodeURIComponent, but it's overkill if IDs should always be numbers.
My Solution is override (request.Request.prototype.qs)
var qs = require('qs'),
request = require('request'),
url = require('url');
var stringify;
var toString = Object.prototype.toString;
var isArray = Array.isArray || function (arr) {
return toString.call(arr) === '[object Array]';
};
var objectKeys = Object.keys || function (obj) {
var ret = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
ret.push(key);
}
}
return ret;
};
var stringifyString = function (str, prefix) {
if (!prefix) throw new TypeError('stringify expects an object');
return prefix + '=' + encodeURIComponent(str);
};
var stringifyArray = function (arr, prefix) {
var ret = [];
if (!prefix) throw new TypeError('stringify expects an object');
for (var i = 0; i < arr.length; i++) {
ret.push(stringify(arr[i], prefix));
}
return ret.join('&');
};
function stringifyObject(obj, prefix) {
var ret = [];
var keys = objectKeys(obj);
var key;
for (var i = 0, len = keys.length; i < len; ++i) {
key = keys[i];
if ('' === key) {
continue;
}
if (null === obj[key]) {
ret.push(encodeURIComponent(key) + '=');
} else {
ret.push(stringify(obj[key], prefix ? prefix + '[' + encodeURIComponent(key) + ']' : encodeURIComponent(key)));
}
}
return ret.join('&');
}
stringify = function (obj, prefix) {
if (isArray(obj)) {
return stringifyArray(obj, prefix);
} else if ('[object Object]' === toString.call(obj)) {
return stringifyObject(obj, prefix);
} else if ('string' === typeof obj) {
return stringifyString(obj, prefix);
} else {
return prefix + '=' + encodeURIComponent(String(obj));
}
};
And override prototype.qs :
request.Request.prototype.qs = function (q, clobber) {
var base;
if (!clobber && this.uri.query) {
base = qs.parse(this.uri.query)
}
else {
base = {}
}
for (var i in q) {
base[i] = q[i]
}
if (stringify(base) === '') {
return this
}
this.uri = url.parse(this.uri.href.split('?')[0] + '?' + stringify(base));
this.url = this.uri;
this.path = this.uri.path;
return this;
};
For example, I have:
var Data = [
{ id_list: 1, name: 'Nick', token: '312312' },
{ id_list: 2, name: 'John', token: '123123' },
]
Then, I want to sort/reverse this object by name, for example. And then I want to get something like this:
var Data = [
{ id_list: 2, name: 'John', token: '123123' },
{ id_list: 1, name: 'Nick', token: '312312' },
]
And now I want to know the index of the object with property name='John' to get the value of the property token.
How do I solve the problem?
Since the sort part is already answered. I'm just going to propose another elegant way to get the indexOf of a property in your array
Your example is:
var Data = [
{id_list:1, name:'Nick', token:'312312'},
{id_list:2, name:'John', token:'123123'}
]
You can do:
var index = Data.map(function(e) { return e.name; }).indexOf('Nick');
var Data = [{
id_list: 1,
name: 'Nick',
token: '312312'
},
{
id_list: 2,
name: 'John',
token: '123123'
}
]
var index = Data.map(function(e) {
return e.name;
}).indexOf('Nick');
console.log(index)
Array.prototype.map is not available on Internet Explorer 7 or Internet Explorer 8. ES5 Compatibility
And here it is with ES6 and arrow syntax, which is even simpler:
const index = Data.map(e => e.name).indexOf('Nick');
If you're fine with using ES6, arrays now have the findIndex function. Which means you can do something like this:
const index = Data.findIndex(item => item.name === 'John');
As the other answers suggest, looping through the array is probably the best way. But I would put it in its own function, and make it a little more abstract:
function findWithAttr(array, attr, value) {
for(var i = 0; i < array.length; i += 1) {
if(array[i][attr] === value) {
return i;
}
}
return -1;
}
var Data = [
{id_list: 2, name: 'John', token: '123123'},
{id_list: 1, name: 'Nick', token: '312312'}
];
With this, not only can you find which one contains 'John', but you can find which contains the token '312312':
findWithAttr(Data, 'name', 'John'); // returns 0
findWithAttr(Data, 'token', '312312'); // returns 1
findWithAttr(Data, 'id_list', '10'); // returns -1
The function returns -1 when not found, so it follows the same construct as Array.prototype.indexOf().
If you're having issues with Internet Explorer, you could use the map() function which is supported from 9.0 onward:
var index = Data.map(item => item.name).indexOf("Nick");
var index = Data.findIndex(item => item.name == "John")
Which is a simplified version of:
var index = Data.findIndex(function(item){ return item.name == "John"})
From mozilla.org:
The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.
Only way known to me is to loop through all array:
var index = -1;
for(var i=0; i<Data.length; i++)
if(Data[i].name === "John") {
index = i;
break;
}
Or case insensitive:
var index = -1;
for(var i=0; i<Data.length; i++)
if(Data[i].name.toLowerCase() === "john") {
index = i;
break;
}
On result variable index contain index of object or -1 if not found.
A prototypical way
(function(){
if (!Array.prototype.indexOfPropertyValue){
Array.prototype.indexOfPropertyValue = function(prop, value){
for (var index = 0; index < this.length; index++){
if (this[index][prop]){
if (this[index][prop] == value){
return index;
}
}
}
return -1;
}
}
})();
// Usage:
var Data = [
{id_list:1, name:'Nick', token:'312312'}, {id_list:2, name:'John', token:'123123'}];
Data.indexOfPropertyValue('name', 'John'); // Returns 1 (index of array);
Data.indexOfPropertyValue('name', 'Invalid name') // Returns -1 (no result);
var indexOfArray = Data.indexOfPropertyValue('name', 'John');
Data[indexOfArray] // Returns the desired object.
you can use filter method
const filteredData = data.filter(e => e.name !== 'john');
Just go through your array and find the position:
var i = 0;
for(var item in Data) {
if(Data[item].name == 'John')
break;
i++;
}
alert(i);
let indexOf = -1;
let theProperty = "value"
let searchFor = "something";
theArray.every(function (element, index) {
if (element[theProperty] === searchFor) {
indexOf = index;
return false;
}
return true;
});
collection.findIndex(item => item.value === 'smth') !== -1
You can use Array.sort using a custom function as a parameter to define your sorting mechanism.
In your example, it would give:
var Data = [
{id_list:1, name:'Nick',token:'312312'},{id_list:2,name:'John',token:'123123'}
]
Data.sort(function(a, b){
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
});
alert("First name is : " + Data[0].name); // alerts 'John'
alert("Second name is : " + Data[1].name); // alerts 'Nick'
The sort function must return either -1 if a should come before b, 1 if a should come after b and 0 if both are equal. It's up to you to define the right logic in your sorting function to sort the array.
Missed the last part of your question where you want to know the index. You would have to loop through the array to find that as others have said.
This might be useful:
function showProps(obj, objName) {
var result = "";
for (var i in obj)
result += objName + "." + i + " = " + obj[i] + "\n";
return result;
}
I copied this from Working with objects.
Use a small workaround:
Create a new array with names as indexes. After that all searches will use indexes. So, only one loop. After that you don't need to loop through all elements!
var Data = [
{id_list:1, name:'Nick',token:'312312'},{id_list:2,name:'John',token:'123123'}
]
var searchArr = []
Data.forEach(function(one){
searchArr[one.name]=one;
})
console.log(searchArr['Nick'])
http://jsbin.com/xibala/1/edit
Live example.
I extended Chris Pickett's answer, because in my case I needed to search deeper than one attribute level:
function findWithAttr(array, attr, value) {
if (attr.indexOf('.') >= 0) {
var split = attr.split('.');
var attr1 = split[0];
var attr2 = split[1];
for(var i = 0; i < array.length; i += 1) {
if(array[i][attr1][attr2] === value) {
return i;
}
}
} else {
for(var i = 0; i < array.length; i += 1) {
if(array[i][attr] === value) {
return i;
}
}
};
};
You can pass 'attr1.attr2' into the function.
Use this:
Data.indexOf(_.find(Data, function(element) {
return element.name === 'John';
}));
It is assuming you are using Lodash or Underscore.js.
var fields = {
teste:
{
Acess:
{
Edit: true,
View: false
}
},
teste1:
{
Acess:
{
Edit: false,
View: false
}
}
};
console.log(find(fields,'teste'));
function find(fields,field) {
for(key in fields) {
if(key == field) {
return true;
}
}
return false;
}
If you have one Object with multiple objects inside, if you want know if some object are include on Master object, just use find(MasterObject, 'Object to Search'). This function will return the response if it exists or not (TRUE or FALSE). I hope to help with this - can see the example on JSFiddle.
If you want to get the value of the property token then you can also try this:
let data=[
{ id_list: 1, name: 'Nick', token: '312312' },
{ id_list: 2, name: 'John', token: '123123' },
]
let resultingToken = data[_.findKey(data,['name','John'])].token
where _.findKey is a Lodash function.
You can use findIndex in Lodash library.
Example:
var users = [
{ 'user': 'barney', 'active': false },
{ 'user': 'fred', 'active': false },
{ 'user': 'pebbles', 'active': true }
];
_.findIndex(users, function(o) { return o.user == 'barney'; });
// => 0
// The `_.matches` iteratee shorthand.
_.findIndex(users, { 'user': 'fred', 'active': false });
// => 1
// The `_.matchesProperty` iteratee shorthand.
_.findIndex(users, ['active', false]);
// => 0
// The `_.property` iteratee shorthand.
_.findIndex(users, 'active');
// => 2
Alternatively to German Attanasio Ruiz's answer, you can eliminate the second loop by using Array.reduce() instead of Array.map();
var Data = [
{ name: 'hypno7oad' }
]
var indexOfTarget = Data.reduce(function (indexOfTarget, element, currentIndex) {
return (element.name === 'hypno7oad') ? currentIndex : indexOfTarget;
}, -1);
Maybe the Object.keys, Object.entries, and Object.values methods might help.
Using Underscore.js:
var index = _.indexOf(_.pluck(item , 'name'), 'Nick');