Assuming I have an object like this:
var result = {
items: [
'item1', 'item2', 'item3'
]
}
I'm looking for a way create an object that would abstract my result object and yet would remain compatible to it:
function wrapper(result) {
this.items = ???
}
In order to able able to do this
var p = new wrapper(result);
for(var x = 0; x < p.items.length; x++) {
console.log(p[x]) // = item1, item2, item3
}
This can be easily accomplished in PHP using ArrayIterator. I was wondering if it can be done in JS, specifically in NodeJS.
Just to clarify:
Imagine the original object is a result from a db query. However, the formatting of that result object cannot be fed into the consumer as-is and needs to be adapted. In this case I did not dwell on the adaptation but for the sake of the example assume each value needs to be uppercased.
Additionally, the original list is very large. I could obviously iterate, copy all values and uppercase them, but in most cases it will not be required for all values, therefore it would be inefficient to do this for all items every time. The idea of the wrapper is to act as a proxy that I would be able to iterate. The wrapper will in turn retrieve the result from the original object, and modify it on the fly.
If you're using Node 4 or 5 you can consider using ES6's Symbol.Iterator. It's the closest thing to PHP's ArrayIterator that I can think of.
Here's an example of a use-case which you describe in your post:
'use strict';
let result = {
items: [
'item1', 'item2', 'item3'
],
[Symbol.iterator](cb) {
let index = 0;
return {
next: () => {
let value = this.items[index];
let done = index >= this.items.length;
// Note that arrow functions won't bind `this.items` to cb's `this`.
if (typeof cb === 'function') {
return cb.call(this.items, value, done, this.items, index++);
} else {
index++;
return { value, done };
}
}
}
}
}
let cb = (value, done, items, index) => {
// Modify original array.
items[index] = items[index] && items[index].toUpperCase();
value = items[index];
return { value, done };
};
let iterator1 = result[Symbol.iterator](cb);
let iterator2 = result[Symbol.iterator]();
console.log(iterator1.next()); // { value: 'ITEM1', done: false }
console.log(iterator2.next()); // { value: 'ITEM1', done: false }
console.log(iterator1.next()); // { value: 'ITEM2', done: false }
console.log(iterator1.next()); // { value: 'ITEM3', done: false }
console.log(result.items); // [ 'ITEM1', 'ITEM2', 'ITEM3' ]
Note that you can define multiple iterators to iterate concurrently, modify the referenced array, and add your own methods to the iterator to emulate ArrayIterator as you please.
I should also mention that the for-of construct works well with this:
'use strict';
let result = {
items: [
'item1', 'item2', 'item3'
]
}
let cb = (value, done, items, index) => {
// Modify original array.
items[index] = items[index] && items[index].toUpperCase();
value = items[index];
return { value, done };
};
let modifiableIterableIterator = {
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
let value = result.items[index];
let done = index >= result.items.length;
// Note that arrow functions won't bind `this.items` to cb's `this`.
if (typeof cb === 'function') {
return cb.call(result.items, value, done, result.items, index++);
} else {
index++;
return { value, done };
}
}
}
}
};
for (let item of modifiableIterableIterator) console.log(item);
modifiableIterableIterator[Symbol.iterator] is the same as the previous result[Symbol.iterator], except there is no more cb parameter. When passing an iterable iterator (an iterable iterator is one that defines a next method, and Symbol.iterator on the object), no arguments are passed to Symbol.iterator, so we just make the callback explicit from within the method. The loop will perform the same operations as before: uppercase all the values and modify the array in-place.
There are some design decisions about how this can be structured properly, but this is up to you and the way your application is structured.
Since you want to support the "older" way to iterate while maintaining this behavior, the only way you can do this by overloading [] is to use ES6's Proxy object. This allows you to do some metaprogramming. Unfortunately, the native support is lacking, but you can use shims if you need to (I got this working on Node 5 using the harmony-reflect module and passing --harmony_proxies on the command-line).
let proxy = new Proxy(result.items, {
get(target, index) {
target[index] = target[index] && target[index].toUpperCase();
return target[index];
}
});
for (let x = 0; x < result.items.length; x++) console.log(proxy[x]); // 'ITEMx'
console.log(result.items); // [ 'ITEM1', 'ITEM2', 'ITEM3' ]
Combine the new iterators feature and a Proxy, and you have a solution which works with your existing implementation and future-proof's your code.
I would recommend creating a class similar to ArrayIterator.
Create an npm module
Put this in your module's index.js:
function wrapper( itemsĀ ) {
this.items = items;
this.count = items.length;
this.iterateReverse = ( cb ) => {
var x = this.count;
while ( x-- ) cb( this.items[ x ] );
}
this.iterate = ( cb ) => {
var x = -1;
while ( x++ < this.count-1 ) cb( this.items[ x ] );
}
}
wrapper.prototype.items = (this.items);
module.exports = wrapper;
Now in your main environment:
const
wrapper = require( './wrapper' );
var p = new wrapper( [ 'item1', 'item2', 'item3' ] );
for( var x = 0; x < p.count; x++ ) {
console.log( p.items[x] ); // = item1, item2, item3
}
p.iterateReverse(( v ) => {
console.log( v ); // = item3, item2, item1
});
p.iterate(( v ) => {
console.log( v ); // = item1, item2, item3
});
This will result with // = item1, item2, item3
Related
I have this array
myarr = [
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...'
];
symbol = indicates that is is a property, next to that is a list of items that belongs to that property
I want to transform that array into object
myObj = {
title1: [
'longText0...',
'longtText1...',
],
title2: [
'longTextA...',
'longtTextB...',
'longtTextC...'
]
}
I come up with this code so far:
const arrayToObject = (array) =>
array.reduce((obj, item) => {
if(item.startsWith('=')) {
const itemName = item.replace('=', '')
obj[itemName] = itemName;
} else {
//add the rest....
}
return obj
}, {})
console.log(arrayToObject(myarr))
My challenges so far is that I am not sure how to turn obj[itemName] so I can assign the items to it. Any ideas how to do that?
A reduce based approach which does not depend on outer scope references for keeping track of the currently to be built/aggregated property makes this information part of the reducer function's first parameter, the previousValue which serves as an accumulator/collector object.
Thus, as for the OP's task, this collector would feature two properties, the currentKey and the result, where the former holds the state of the currently processed property name and the latter being the programmatically built result.
// - reducer function which aggregates entries at time,
// either by creating a new property or by pushing a
// value into the currently processed property value.
// - keeps the state of the currently processed property
// by the accumulator`s/collector's `currentKey` property
// whereas the result programmatically gets build as
// the accumulator`s/collector's `result` property.
function aggregateEntry({ currentKey = null, result = {} }, item) {
const key = (item.startsWith('=') && item.slice(1));
if (
(key !== false) &&
(key !== currentKey)
) {
// keep track of the currently processed property name.
currentKey = key;
// create a new entry (key value pair).
result[currentKey] = [];
} else {
// push value into the currently processed property value.
result[currentKey].push(item);
}
return { currentKey, result };
}
console.log([
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...',
].reduce(aggregateEntry, { result: {} }).result);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I wouldn't do that with reduce but with a simple for loop, because you have to carry the itemname over multiple iterations
let o = {}, n = '';
for (let k of arr) {
if (k.startsWith('=')) {
n = k.substring(1);
o[n] = []
} else {
o[n].push(k);
}
}
You can of course also do it with reduce, but you have to put the declaration of itemname outside of the callback
let n = '';
let o = arr.reduce((a, c) => {
if (c.startsWith('=')) {
n = c.substring(1);
a[n] = [];
} else {
a[n].push(c);
}
return a;
}, {});
Please be aware, there is no error handling, ie the code assumes your array is well structured and the first element in the array must start with =
The following function will give you the desired results
function arrayToObject(arr)
{
let returnObj={};
for(let i =0; i <arr.length; i++)
{
if(arr[i].startsWith('='))
{
let itemName = arr[i].replace('=','');
returnObj[itemName]=[];
for(let j=i+1; j <arr.length;j++)
{
if(arr[j].startsWith('='))
{
break;
}
else
{
let value = arr[j];
returnObj[itemName].push(value) ;
}
}
}
}
return returnObj;
}
Here a version with reduce
const myarr = [
'=title1',
'longText0...',
'longtText1...',
'=title2',
'longTextA...',
'longtTextB...',
'longtTextC...'
];
const obj = myarr.reduce((res, el) => {
if(el.startsWith('=')){
const key = el.substring(1)
return {
data: {
...res.data,
[key]: [],
},
key
}
}
return {
...res,
data:{
...res.data,
[res.key]: [...res.data[res.key], el]
}
}
}, {
data: {},
key: ''
}).data
console.log(obj)
You don't need to keep the key somewhere separate for a reduce method:
const myarr = ['=title1', 'longText0...', 'longtText1...', '=title2', 'longTextA...', 'longtTextB...', 'longtTextC...'];
const res = Object.fromEntries(
myarr.reduce((acc, item) => {
if(item.startsWith('='))
acc.push([item.substring(1), []]);
else
acc[acc.length - 1]?.[1].push(item);
return acc;
}, [])
);
console.log(JSON.stringify( res ));
I'm trying to convert an environment variable into an object of values for configuration in JavaScript but I don't know the best way to achieve this.
The idea would be to output SAMPLE_ENV_VAR=value as:
{
sample: {
env: {
var: value
}
}
}
What I have so far:
const _ = require('lodash');
const process = require('process');
_.each(process.env, (value, key) => {
key = key.toLowerCase().split('_');
// Convert to object here
}
Here's a more complete solution based on yours:
const _ = require('lodash');
const result = {};
// We'll take the following as an example:
// process.env = { HELLO_WORLD_HI: 5 }
// We'll expect the following output:
// result = { hello: { world: { hi: 5 } } }
_.each(process.env, (value, key) => {
// We'll separate each key every underscore.
// In simple terms, this will turn:
// "HELLLO_WORLD_HI" -> ['HELLO', 'WORLD', 'HI']
const keys = key.toLowerCase().split('_');
// We'll start on the top-level object
let current = result;
// We'll assign here the current "key" we're iterating on
// It will have the values:
// 'hello' (1st loop), 'world' (2nd), and 'hi' (last)
let currentKey;
// We'll iterate on every key. Moreover, we'll
// remove every key (starting from the first one: 'HELLO')
// and assign the removed key as our "currentKey".
// currentKey = 'hello', keys = ['world', 'hi']
// currentKey = 'world', keys = ['hi'], and so on..
while ( (currentKey = keys.shift()) ) {
// If we still have any keys to nest,
if ( keys.length ) {
// We'll assign that object property with an object value
// result =// { HELLO: {} }
current[currentKey] = {};
// And then move inside that object so
// could nest for the next loop
// 1st loop: { HELLO: { /*We're here*/ } }
// 2nd loop: { HELLO: { WORLD: { /*We're here*/ } } }
// 3rd loop: { HELLO: { WORLD: { HI : { /*We're here*/ } } } }
current = current[currentKey];
} else {
// Lastly, when we no longer have any key to nest
// e.g., we're past the third loop in our example
current[currentKey] = process.env[key]
}
}
});
console.log(result);
To simply put:
We'll loop through every environment variable (from process.env)
Split the key name with an underscore, and, again, loop each key (['HELLO', 'WORLD', 'HI'])
Assign it to an object ({ hello: {} } -> { hello: { world: {} } } -> { hello: world: { hi: ? } } })
When we no longer have any keys left, assign it to the actual value ({ hello: { world: { hi: 5 } } })
Funnily enough, I just finished code for this last night for a personal project. What I ended up using is not ideal, but is working for me:
export function keyReducer(
src: any,
out: any,
key: string,
pre: string,
del: string
): ConfigScope {
const path = key.toLowerCase().split(del);
if (path[0] === pre.toLowerCase()) {
path.shift();
}
if (path.length === 1) { // single element path
const [head] = path;
out[head] = src[key];
} else {
const tail = path.pop();
const target = path.reduce((parent: any, next: string) => {
if (parent[next]) {
return parent[next];
} else {
return (parent[next] = <ConfigScope>{});
}
}, out);
target[tail] = src[key];
}
return out;
}
static fromEnv(env: Environment, {prefix = 'ABC', delimiter = '_'} = {}) {
const data: ConfigScope = Object.keys(env).filter(key => {
return StringUtils.startsWith(key, prefix);
}).reduce((out, key) => {
return keyReducer(env, out, key, prefix, '_');
}, <ConfigScope>{});
return new Config(data);
}
(with TypeScript type annotations)
The idea here is to split each key, create the target objects on the way down, then set the final value.
This is my quick take at it:
var object = {}; // the object to store the value in
var name = "SAMPLE_ENV_VAR"; // the environment variable key
var value = "value"; // the value of the environment variable
// helper function to automatically create an inner object if none exists
function getOrCreateInnerObj(obj, name) {
if (!obj.hasOwnProperty()) {
obj[name] = {};
}
return obj[name];
}
// an array of the individual parts (e.g. ["sample", "env", "var"])
var keyParts = name.toLowerCase().split("_");
// innerObj will contain the second to last element object in the tree based on the array of keys
var innerObj = getOrCreateInnerObj(object, keyParts[0]);
for (var i = 1; i < keyParts.length - 1; i++) {
innerObj = getOrCreateInnerObj(innerObj, keyParts[i]);
}
// set the value itself
innerObj[keyParts[keyParts.length - 1]] = value;
$("body").html(JSON.stringify(object));
The gist of it is, for all but the last element in the array of key parts, you get or create an object in the current parent object for that key, and once you've repeated this for all but the last key, you'll have the second-to-last inner object, which you can then set the value on.
Edit: Working example
Edit 2: Here is a much cleaner example that uses a little bit of recursion to accomplish the same thing
const basic = {};
let current;
`YOUR_VARIABLE_NAME`
.split(`_`)
.forEach((item, index, array) => {
if(index === 0) {
return current = basic[item] = {};
}
if(index === array.length - 1) {
return current[item] = process.env.HE_LO_NA;
}
current = current[item] = {};
});
console.log(require('util').inspect(basic, {depth: 10}));
const _ = require('lodash');
const process = require('process');
const result = Object.entries(process.env).reduce((acc, [key, value]) => {
_.set(acc, key.toLowerCase().replace('_', '.'), value);
return acc;
}, {})
I am building a little project of mine and have found no other solution to having a key value relationship with the order of iteration guaranteed.
What I have come up with so far is this,
var Plate = function () {
this.children = [];
this.childrenKeys = [];
};
var proto = Plate.prototype;
proto.addChild = function (key, child) {
this.childrenKeys.push(key);
this.children.push(child);
};
proto.removeChild = function (key) {
var index = this.childrenKeys.indexOf(key);
delete(this.children[index]);
};
proto.update = function () {
this.children.forEach(function (child) {
child.update();
});
};
While this does work and I can add to Plate with a key and a value, and I can delete from the plate with a key it just doesn't seem right to do so.
I understand I cannot use an object and for...in as the order of iteration is completely inconsistent and I cannot have it be like that.
Is this the correct method for doing this or is there a build in javascript way of doing this?
Thank you
Why not use an array of objects where the objects contain the key and value?
var list = [
{ key: '2', value: 'foo' },
{ key: 'abc', value: 'bar' }
];
// Usage
alert(list[0].key); // '2'
alert(list[0].value); // 'foo'
Here is your code using this method (untested).
var Plate = function () {
this.list = []
};
var proto = Plate.prototype;
proto.addChild = function (key, child) {
this.list.push({ key: key, child: child });
};
proto.removeChild = function (key) {
for (var i = 0; i < this.list.length; i++) {
if (this.list[i].key === key) {
delete(this.list[i]);
return;
}
}
};
proto.update = function () {
for (var i = 0; i < this.list.length; i++) {
this.list[i].child.update();
}
};
I am aware that I could use .filter to achieve this, however I am not sure how to implement it.
I have objects as follows within an array
item {
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
}
however, some items in the array have a possible property of NaN.
I need to make sure that only items whose possible property is not NaN, are pushed into the array.
Here is an excerpt of my complete code:
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
if(g.attributes.possible !== g.attributes.possible){
return;
}
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
NaN is the only property in javascript that does not equal itself. Just loop over the properties and check them for this, or use the built in NaN() function within the loop as suggested elsewhere.
Update
Since you're only worried about the possible property, just check that one as part of the if statement using === self, or isNaN()
Just change your test line from
if(itemPath[i].attributes.id==id)
to use isNaN function on the properties you want to check
var attr = itemPath[i].attributes;
if (attr.id==id && !isNaN(attr.title) && !isNaN(attr.categoryid) && !isNaN(attr.possible))
You can use the isNaN() and test it before adding it...
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
if( isNaN(g.attributes.title) || isNaN(g.attributes.categoryid) || isNaN(g.attributes.possible) ){
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
}
});
}
}
}
You're code is a little confusing
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
items.push(
{
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
It doesn't look like you are using map right. Map works like list compresions in the sense that it iterates over a sequence to preform some kind of operation on each element and returns a new sequence
var arr = [1,2,3];
var complexMagic = arr.map( function(n) { return n + 10; } );
// complexMagic === [11,12,13]
FYI, this is how filter, works. Filter takes in a predicate function( aka, Boolean function) to build a new sequence. If the predicate returns true, then the element will be stored in the new sequence.
var arr = [1, 123, 42, 1001, 1100];
var oddNumbers = arr.filter( function(n) {
return 1 === (n & (-n) );
} );
// oddNumbers === [1, 123, 1001] );
// Bit hacks are fun ;P
It looks like you don't need items array or to even push new elements onto it.
function load(id){
itemPath = lev1.lev2.lev3;
items = [];
for (var i = 0; i<itemPath.length; i++) {
if(itemPath[i].attributes.id==id) {
return itemPath[i].attributes.grades.models.map(function(g) {
// You don't have to return anything.
// This might be an ok place for the NaN check.
return ({
title : g.attributes.title,
category : g.attributes.categoryid,
possible: g.attributes.possible
});
});
}
}
}
I'm lazy and didn't testing any of my code so reader beware. Also avoid the push method if possible. It can be a inefficient approach to append new elements onto an array.
I have an associative array like:
var arr = {};
arr['alz'] = '15a';
arr['aly'] = '16b';
arr['alx'] = '17a';
arr['alw'] = '09c';
I need to find the previous and next key of any selected element. Say, for key 'aly' it will be 'alz' and 'alx'. If possible, I want to access the array by index rather than the key.
Currently, I am doing this using a separate array containing keys, e.g.
var arrkeys = ['alz','aly','alx','alw'];
Ordering of the object's properties is undefined. You can use this structure...
[{ key: 'alz', value: '15a'},
{ key: 'aly', value: '16b'},
{ key: 'alx', value: '17a'}]
... though searching for the element with the given key (like 'give me the element which key is 'alz') is not as straight-forward as with simple object. That's why using it like you did - providing a separate array for ordering of the indexes - is another common approach. You can attach this array to that object, btw:
var arr={};
arr['alz']='15a';
arr['aly']='16b';
arr['alx']='17a';
arr['alw']='09c';
arr._keysOrder = ['alz', 'aly', 'alx', 'alw'];
This is an object, not an array, and it sounds like you don't really want those strings to be keys.
How about a nice array?
var ar = [
{ key: 'alz', value: '15a' },
{ key: 'aly', value: '16b' },
{ key: 'alx', value: '17a' },
{ key: 'alw', value: '09c' }
];
How about adding some syntactic sugar in the form of an OrderedObject object? Then you could do something like this:
myObj = new OrderedObject();
myObj.add('alz', '15a');
myObj.add('aly', '16b');
myObj.add('alx', '17a');
myObj.add('alw', '09c');
console.log(myObj.keyAt(2)); // 'alx'
console.log(myObj.valueAt(3)); // '09c'
console.log(myObj.indexOf('aly')); // 1
console.log(myObj.length()) // 4
console.log(myObj.nextKey('aly')); // 'alx'
The following code makes this work. See it in action in a jsFiddle.
function OrderedObject() {
var index = [];
this.add = function(key, value) {
if (!this.hasOwnProperty(key)) {
index.push(key);
}
this[key] = value;
};
this.remove = function(key) {
if (!this.hasOwnProperty(key)) { return; }
index.splice(index.indexOf(key), 1);
delete this[key];
}
this.indexOf = function(key) {
return index.indexOf(key);
}
this.keyAt = function(i) {
return index[i];
};
this.length = function() {
return index.length;
}
this.valueAt = function(i) {
return this[this.keyAt(i)];
}
this.previousKey = function(key) {
return this.keyAt(this.indexOf(key) - 1);
}
this.nextKey = function(key) {
return this.keyAt(this.indexOf(key) + 1);
}
}
I made some decisions that may not work for you. For example, I chose to use an Object as the prototype rather than an Array, so that you could preserve enumerating your object with for (key in myObj). But it didn't have to be that way. It could have been an Array, letting you use the property .length instead of the function .length() and then offering an each function that enumerates the keys, or perhaps an .object() function to return the inner object.
This could be a little awkward as you'd have to remember not to add items to the object yourself. That is, if you do myObj[key] = 'value'; then the index will not be updated. I also did not provide any methods for rearranging the order of things or inserting them at a particular position, or deleting by position. If you find my object idea useful, though, I'm sure you can figure out how to add such things.
With the newer versions of EcmaScript you can add true properties and make them non-enumerable. This would allow the new object to more seamlessly and smoothly act like the ideal OrderedObject I am imagining.
If you have to know the order of everything, and still use the keys and values, try this:
var arr = [
{ key: 'alz', value: '15a' },
{ key: 'aly', value: '16b' },
{ key: 'alx', value: '17a' },
{ key: 'alw', value: '09c' }
];
You can then access them sequentially as follows: arr[0].key and arr[0].value. Similarly, you can find siblings inside of the loop with the following:
for(var i = 0; i < arr.length; i++)
{
var previous_key = (i > 0) ? arr[(i - 1)].key : false;
var next_key = (i < (arr.length - 1)) ? arr[(i + 1)].key : false;
}
You may try this
function sortObject(obj, order)
{
var list=[], mapArr = [], sortedObj={};
for(var x in obj) if(obj.hasOwnProperty(x)) list.push(x);
for (var i=0, length = list.length; i < length; i++) {
mapArr.push({ index: i, value: list[i].toLowerCase() });
}
mapArr.sort(function(a, b) {
if(order && order.toLowerCase()==='desc')
return a.value < b.value ? 1 : -1;
else return a.value > b.value ? 1 : -1;
});
for(var i=0; i<mapArr.length;i++)
sortedObj[mapArr[i].value]=obj[mapArr[i].value];
return sortedObj;
}
// Call the function to sort the arr object
var sortedArr = sortObject(arr); // Ascending order A-Z
var sortedArr = sortObject(arr, 'desc'); // Descending order Z-A
DEMO.
Remember, this will return a new object and original object will remain unchanged.