How to add prototypal inheritance to generated objects - javascript

I'm learning about prototypal inheritance and I've read numerous articles and watched multiple videos on the subject. It's starting to make sense now.
The following code example, which can be found at this JSFiddle, takes an array of objects and for each object, it sets a key in a Map object, with an object as its value.
The value object has two methods, get and set, which get and set values to another map object from the local scope of the template factory that returns the object.
My understanding is if we used this example to hypothetically generate 100k objects in the databaseMap, each object would have its own set and get methods, using unnecessary amounts of memory? My understanding is this is where I should be using prototypal inheritance?
So my question is, how do I move the set and get methods to say the scope of the init function and set them to the prototype of the object returned from template, so that when called, they set and get on the storeMap Map, from within the scope of the template function that generated the object?
Or, am I not understanding this correctly?
const config = [
{
id: 'obj1',
value: 'value1',
},
{
id: 'obj2',
value: 'value2',
},
{
id: 'obj3',
value: 'value3',
},
{
id: 'obj4',
value: 'value4',
},
]
function init() {
const databaseMap = new Map()
function template(storeConfig) {
const { id } = storeConfig
const storeMap = new Map()
return {
id,
set(key, value) {
console.log(`Setting data to store ${id}.`)
// Do future work here
return storeMap.set(key, value)
},
get(key) {
// Do future work here
return storeMap.get(key)
},
}
}
config.forEach(x => {
const store = template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('testing1', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log('GET TESTING 1')
console.log(getStore.get('testing1'))

Have you considered just using a single object with get and set defined on it? Instead of creating a new object your can create a new Template() that — then you can take advantage of the way this works in javascript to allow it to work for all instances while avoiding having to capture a new closure for each function.
For example:
const config = [{id: 'obj1', value: 'value1'}, {id: 'obj2', value: 'value2'},{id: 'obj3',value: 'value3',},{id: 'obj4',value: 'value4',}]
function init() {
const databaseMap = new Map()
// a single proto object
const protoObj = {
set(key, value) {
console.log(`Setting data to store ${this.id}.`)
// Do future work here
return this.storeMap.set(key, value)
},
get(key) {
return this.storeMap.get(key)
}
}
function Template(storeConfig) {
this.id = storeConfig.id
this.storeMap = new Map()
}
// use the object for the prototype
Template.prototype = protoObj
config.forEach(x => {
const store = new Template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('aTest', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log('GET aTest')
console.log(getStore.get('aTest'))
Of course you can also define the function directly on the prototype:
Template.prototype.set = function (key, value) {
return this.storeMap.set(key, value)
}
Template.prototype.get = function (key, value) {
return this.storeMap.get(key, value)
}
EDIT based on comment
You can define the functions on their own in such a way that they use this to access the object's properties. Then you can just add a reference from them to the object. Like:
const config = [{id: 'obj1', value: 'value1'}, {id: 'obj2', value: 'value2'},{id: 'obj3',value: 'value3',},{id: 'obj4',value: 'value4',}]
function init() {
const databaseMap = new Map()
function set(key, value) {
console.log(`Setting data to store ${this.id}.`)
// Do future work here
return this.storeMap.set(key, value)
}
function get(key) {
// Do future work here
return this.storeMap.get(key)
}
function template(storeConfig) {
return {
id: storeConfig.id,
storeMap:new Map(),
set:set,
get:get
}
}
config.forEach(x => {
const store = template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('TestKey', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log('TestKey')
console.log(getStore.get('TestKey'))
ALTERNATIVE
const config = [{id: 'obj1', value: 'value1'}, {id: 'obj2', value: 'value2'},{id: 'obj3',value: 'value3',},{id: 'obj4',value: 'value4',}]
function init() {
const databaseMap = new Map()
// a single proto object
const protoObj = {
set(key, value) {
console.log(`Setting data to store ${this.id}.`)
// Do future work here
return this.storeMap.set(key, value)
},
get(key) {
return this.storeMap.get(key)
}
}
function template(storeConfig) {
const id = storeConfig.id
const storeMap = new Map()
return Object.assign(Object.create(protoObj), { id, storeMap })
}
config.forEach(x => {
const store = template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('aTest', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log("PROTOTYPE OF getStore")
console.log(Object.getPrototypeOf(getStore))
console.log('GET aTest')
console.log(getStore.get('aTest'))

Related

How to add a property to a dynamically changing object from an API?

I hope someone can help me with my problem! I didn't find the right thing through the search and maybe someone can give me a hint.
I am calling an API that returns an object that in turn contains nested objects. In these nested objects there are two properties "value" and "scale". I want to divide these two properties and write them as properties in the same object.
The data I get from the API is dynamic, which means it is constantly changing.
Example:
// call api
const apiCall = callApi(...);
// return object
console.log(apiCall);
{
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
}
// targed object
const objTarged = {
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
result: 24.59 // value / scale = result
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
result: 0.1459 // value / scale = result
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
};
My thoughts:
do I need to map the object into a new object?
how can I do this if the source object is constantly changing (Object.values?)
How can I write the result of Value / Scale as a new property in the same object each time I call the API?
Big thanks in advance :)
It might be helpful to decompose the problem into first finding the nested objects with the keys you're interested in. Having done that, it will be easy to augment those objects with the desired calculation.
Below is a sort of generic function that finds a nested object based on it having a particular key. With that, fixMyApiData writes itself...
// return an array of objects that are nested in the passed object which contain the passed key
function objectsContainingKey(object, key) {
let results = [];
Object.keys(object).forEach(k => {
if (k === key) results.push(object);
if (object[k] && typeof object[k] === 'object')
results = results.concat(objectsContainingKey(object[k], key));
});
return results;
}
// find the nested objects we care about and augment them with the value/scale calculation
function fixMyApiData(apiData) {
objectsContainingKey(apiData, 'scale').forEach(data => {
if (data.value) data.result = data.value / data.scale;
})
}
let apiData = {
id: '3454353458764389759834534534',
json_data: {
persons: {
de: {
name: 'Bob',
data: {
scale: 100,
value: 2459,
},
},
be: {
name: 'Alice',
data: {
scale: 10000,
value: 1459,
},
},
},
url: 'https://stackoverflow.com/',
timestamp: '2021-10-23T12:00:11+00:00',
disclaimer: 'Some bla bla',
},
};
fixMyApiData(apiData);
console.log(apiData);
I would create a mapValues() function that takes an object, and creates a new object by passing each of the object's values in a transforming function.
Whenever the api call returns a new object, we recreate the new object with the result property according to the structure.
How does the mapValues function works?
Whenever an object (or array) is passed to mapValues, it's converted to an array of [key, value] pairs. The pairs are then mapped to new [key, pair] entries by applying transformFn to the value. The transform array of pairs is then converted back to an using Object.fromEntries().
const mapValues = (transformFn, obj) => Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, transformFn(value)])
)
const apiCall = {"persons":{"de":{"name":"Bob","scale":100,"value":2459},"be":{"name":"Alice","scale":10000,"value":1459}}}
const result = mapValues(
val => mapValues(v => ({
...v,
result: v.value / v.scale,
}), val),
apiCall
)
console.log(result)
If you have multiple nested levels with properties you don't want to transform, we can also pass the key to the transformFn for a more granular change. Now we can create a recursive function to traverse the tree, and only update objects which have a specific key.
const mapValues = (transformFn, obj) => Object.fromEntries(
Object.entries(obj)
.map(([key, value]) => [key, transformFn(value, key)])
)
const fn = obj => mapValues(
(val, key) => {
// if the key is data create a new object with a result property
if(key === 'data') return ({
...val,
result: val.value / val.scale,
})
// if it's object pass it to the recursive function
if(typeof val === 'object') return fn(val)
return val
},
obj
)
const apiCall = {"id":"3454353458764389759834534534","json_data":{"persons":{"de":{"name":"Bob","data":{"scale":100,"value":2459}},"be":{"name":"Alice","data":{"scale":10000,"value":1459}}},"url":"https://stackoverflow.com/","timestamp":"2021-10-23T12:00:11+00:00","disclaimer":"Some bla bla"}}
const result = fn(apiCall)
console.log(result)

Cannot assign to read only property with .map(). Recoil with NextJs

before I use only nextJs everything is good to go but after I try to use recoil and I try to assign new value to array object by using .map() but the error show up
Cannot assign to read only property
Here is my example Array object
const [allData, setAllData] = useRecoilState(
allDataStatte
);
Here is example state AllData
const allData = [
{
id:1,
value:"test1"
},
{
id:2,
value:"test2"
}
]
Here is my code
const edit = (listId, value) => {
allData.map((data) => {
if (data.id === listId) {
data.value = value;
}
});
};
example I want to call edit funcion like this
edit(1,"newTitle1")
I want my new allData output look like this
const data = [
{
id:1,
value:"newTitle1"
},
{
id:2,
value:"test2"
}
]
I have read someone told that I have to use .slice() to create new object but still not use how to use slice with an array object
Here is what you need to do,
const [allData, setAllData] = useRecoilState(allDataState);
const edit = (listId : number, value : string) => {
let newAllData = allData.map((data) => {
let newData = {...data};
if (data.id === listId) {
newData.value = value;
}
return newData;
});
setAllData (newAllData);
};
edit(1, 'new value 1');
Noticed, newAllData is a new array. Also newData is a new object constructed from data.
it's because of atom in recoil you have to re create object array and then setState again by using _clondeep or slice

Merge two objects by matching key while following Flow rules

I have created a function as shown below, which should append the array value into the first object.
Let's say we have given data below:
subjects = {
student1: ['Math', 'Science'],
student2: ['Math', 'Physics', 'English'],
};
students = {
student1: {
// other data
subjectsList: [],
},
student2: {
// other data
subjectsList: [],
},
};
Function code below:
const merge = (subjects: Object, students: Object) => {
Object.keys(subjects).forEach((id: Object) => {
const subjectsList = subjects[id];
const student = students[id];
if (student) {
const updatedStudent = {
...student,
subjectsList,
};
students[id] = updatedStudent;
}
});
return students;
};
This would result in a flow error:
Cannot access the computed property using object type [1].
app/reducers/subjects.reducer.js:42:32
42| const student = students[id];
^^
References:
app/reducers/subjects.reducer.js:40:48
40| Object.keys(subjects).forEach((id: Object) => {
^^^^^^ [1]
Object.keys(subjects).forEach((id: Object)
The id in the .forEach((id) => is not an Object, but a String
If you remove the typehinting (or whatever it is called).
const merge = (subjects, students) => {
Object.keys(subjects).forEach((id) => {
const subjectsList = subjects[id];
const student = students[id];
if (stand) {
const updatedStudent = {
...student,
subjectsList,
};
students[id] = updatedStudent;
}
});
return students;
};
I think this will work.

Javascript proxy a collection to single object

I've never used Proxy before, but I think it should be possible to "merge" an collection of objects into a single object.
It needs to remain "live" because the original fields will have value changes performed on them.
Ignore key collisions at this stage:
Given:
const fields = [{
name: 'hello',
value: 1
},{
name: 'goodbye',
value : 2
}];
Output:
const proxy = { hello:1 , goodbye :2 }
I definitely need to be able to iterate over the proxied object with a for in.
Have start a pen here, but haven't got very far: https://codepen.io/anon/pen/mMRaKw?editors=1111
Is it possible?
Here is a solution with the Proxy target as an empty object (if the Array is proxied, the for in will iterate numbered entries).
const fields = [{name: 'hello',value: 1}, { name: 'goodbye', value: 2}];
let handler = {
get: function(target, name) {
var f = fields.find(f => f.name === name);
return f && f.value;
},
ownKeys: function(target) {
return fields.map(f => f.name);
},
getOwnPropertyDescriptor: function(target, prop) {
return { configurable: true, enumerable: true };
}
};
let prox = new Proxy({}, handler);
// update original
fields[0].value=10;
// change reflected in proxy
console.log('proxy.hello',prox.hello);
for( let i in prox ){
console.log(i)
console.log(prox[i])
}
console.log(prox)
I think you are looking for something like:
const fields = [{name: 'hello',value: 1}, { name: 'goodbye', value: 2}];
let handler = {
get: function(target, name) {
if (name === 'flatten') {
return target.reduce((a, c) => {
a[c.name] = c.value;
return a
}, {});
} else {
return target[name];
}
},
set: function(target, prop, value) {
let obj = target.find(o => o.name === prop);
if(obj) {
obj.value = value;
return true;
}
return false;
}
};
let prox = new Proxy(fields, handler);
console.log('flat obj', JSON.stringify(prox.flatten))
// update original
fields[0].value=10;
// change reflected in proxy
console.log('flatten.hello',prox.flatten.hello);
// update proxy
prox.goodbye = 200;
// change reflected in original
console.log('original', fields[1].value)

How can I get console.log to output the getter result instead of the string "[Getter/Setter]"?

In this code:
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
};
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
I would like to get { _id: 123, id: 123 }
but instead I get { _id: 123, id: [Getter/Setter] }
Is there a way to have the getter value be used by the console.log function?
You can use console.log(Object.assign({}, obj));
Use console.log(JSON.stringify(obj));
Since Nodejs v11.5.0 you can set getters: true in the util.inspect options. See here for docs.
getters <boolean> | <string> If set to true, getters are inspected. If set to 'get', only getters without a corresponding setter are inspected. If set to 'set', only getters with a corresponding setter are inspected. This might cause side effects depending on the getter function. Default: false.
You can define an inspect method on your object, and export the properties you are interested in. See docs here: https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
I guess it would look something like:
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
};
Cls.prototype.inspect = function(depth, options) {
return `{ 'id': ${this._id} }`
}
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
I needed a pretty printed object without the getters and setters yet plain JSON produced garbage. For me as the JSON string was just too long after feeding JSON.stringify() a particularly big and nested object. I wanted it to look like and behave like a plain stringified object in the console. So I just parsed it again:
JSON.parse(JSON.stringify(largeObject))
There. If you have a simpler method, let me know.
On Node.js, I suggest using util.inspect.custom, which will allow you to pretty print getters as values, while keeping other properties output unchanged.
It will apply to your specific object only and won't mess the general console.log output.
The main benefit vs Object.assign is that it happens on your object, so you keep the regular generic console.log(object) syntax. You don't have to wrap it with console.log(Object.assign({}, object)).
Add the following method to your object:
[util.inspect.custom](depth, options) {
const getters = Object.keys(this);
/*
for getters set on prototype, use instead:
const prototype = Object.getPrototypeOf(this);
const getters = Object.keys(prototype);
*/
const properties = getters.map((getter) => [getter, this[getter]]);
const defined = properties.filter(([, value]) => value !== undefined);
const plain = Object.fromEntries(defined);
const object = Object.create(this, Object.getOwnPropertyDescriptors(plain));
// disable custom after the object has been processed once to avoid infinite looping
Object.defineProperty(object, util.inspect.custom, {});
return util.inspect(object, {
...options,
depth: options.depth === null ? null : options.depth - 1,
});
}
Here is a working example in your context:
const util = require('util');
function Cls() {
this._id = 0;
Object.defineProperty(this, 'id', {
get: function() {
return this._id;
},
set: function(id) {
this._id = id;
},
enumerable: true
});
this[util.inspect.custom] = function(depth, options) {
const getters = Object.keys(this);
/*
for getters set on prototype, use instead:
const prototype = Object.getPrototypeOf(this);
const getters = Object.keys(prototype);
*/
const properties = getters.map((getter) => [getter, this[getter]]);
const defined = properties.filter(([, value]) => value !== undefined);
const plain = Object.fromEntries(defined);
const object = Object.create(this, Object.getOwnPropertyDescriptors(plain));
// disable custom after the object has been processed once to avoid infinite looping
Object.defineProperty(object, util.inspect.custom, {});
return util.inspect(object, {
...options,
depth: options.depth === null ? null : options.depth - 1,
});
}
};
var obj = new Cls();
obj.id = 123;
console.log(obj);
console.log(obj.id);
Output:
Cls { _id: 123, id: 123 }
123
Use spread operator:
console.log({ ... obj });

Categories

Resources