Javascript : class instantiation with json - javascript

I have a class "House" like :
class House{
constructor(params){
this.clear();
// this = {...params} // I know that don't work !!!
//--
// if(params.address !== undefined) this.address = {...params.address}
//...
}
clear(){
this.address = {
number: null,
street: null,
zipcode: null,
ton: null,
}
this.access = {
doorcode: null,
stair: null,
}
}
}
I want to create a new instance of House and inject in constructor multiple json like :
const h = new House({address: { /* json */ }, access: { /* json */});
Or only one like :
const h = new House({access: { /* json */});
In constructor, am i obliged to check all values in "params" to insert in good properties (nested object)
I would like to avoid to create other classes like address and access and in the house constructor create new instance of each.
What's the best practice ?
Regards

Using Object.assign() and object destructuring with default parameters in the constructor, you can achieve this quite easily:
class House {
static get defaultAddress () {
return {
number: null,
street: null,
zipcode: null,
town: null
}
}
static get defaultAccess () {
return {
doorcode: null,
stair: null
}
}
constructor({ address = House.defaultAddress, access = House.defaultAccess } = {}) {
this.clear()
Object.assign(this.address, address)
Object.assign(this.access, access)
}
clear () {
const { defaultAddress, defaultAccess } = House
Object.assign(this, { address: defaultAddress, access: defaultAccess })
}
}
// no object
console.log(new House())
// empty object
console.log(new House({}))
// partial object
console.log(new House({ address: { number: 1, street: 'street', zipcode: 12345, town: 'town' } }))
// empty sub-objects
console.log(new House({ address: {}, access: {} }))
// partial sub-objects
console.log(new House({ address: { number: 1, street: 'street' }, access: { doorcode: 321 } }))
// complete object
console.log(new House({ address: { number: 1, street: 'street', zipcode: 12345, town: 'town' }, access: { doorcode: 321, stair: 3 } }))
.as-console-wrapper{min-height:100%!important}

You can loop through the parameters and set them manually. Then, to clear, remove all own properties (properties that aren't inherited).
class House {
constructor(params) {
// set data
Object.assign(this, params);
}
clear() {
for (let key in this) {
if (this.hasOwnProperty(key))
this[key] = undefined; // or `delete this[key];`
}
}
}
let house = new House({type: "normal", height: 40});
console.log(house, house instanceof House);
Of course, you probably want to limit the input keys to a predefined set. You could store those keys in a static class variable and use them to loop through the properties in constructor and clear.
class House {
constructor(params) {
// check for invalid properties
Object.keys(params).forEach(key => {
if (!House.keys.includes(key))
throw `Invalid paramater ${key}`;
});
// set data
Object.assign(this, params);
}
clear() {
for (let key in House.keys) {
if (this.hasOwnProperty(key))
this[key] = undefined; // or `delete this[key];`
}
}
}
House.keys = ['type', 'height'];
let house = new House({type: 'normal', height: 40});
console.log(house, house instanceof House);
let error = new House({helloWorld: true});

I think you want a common namespace for your instance properties - similar to React's props pattern - you can also specify defaults for each instance you are creating:
const defaultProps = { address: {}, access: {} };
class House {
constructor(props = {}) {
this.props = {...defaultProps, ...props};
}
clear() {
this.props = {...defaultProps};
}
}

Related

spread and destructuring object

I have this code :
function logInfos(user = {}) {
const redactedUser = {
firstName: "<REDACTED>",
lastName: "<REDACTED>",
address: {
city: "<REDACTED>",
country: "<REDACTED>",
},
};
const {
firstName,
lastName,
address: { city, country },
} = user;
console.log("partie user", firstName);
console.log("partie user", lastName);
const newUser = {
...user,
address: {
...user.address,
},
};
console.log("partie newuser", newUser);
console.log(`${newUser.firstName} ${newUser.lastName} lives in ${newUser.address.city}, ${newUser.address.country}.`);
}
How can I replace the value undefined of user object passed as argument and use the default value of redactedUser object instead?
function compareObj(defaultObj, targetObj) {
for(let key in defaultObj) {
if(!Array.isArray(defaultObj[key]) && defaultObj[key] !== null && typeof defaultObj[key] === "object") {
targetObj[key] = {};
copyObj(defaultObj[key], targetObj[key]);
} else {
if(!targetObj[key]) targetObj[key] = defaultObj[key];
}
}
}
compareObj(redactedUser, user);
You can place this code function inside logInfos.
What it does is, it iterates through all the properties of the default object and checks if the property exists in the target object.
In case the property does not exist in the target object, same property will be created in the target object and the value will be copied.

Creating a obj name with data in it in vue.js

I just created a constructor function to create new Users for a JSON file.
The structure should be like:
{
"users": {
"userName1": {
"salary": [
"1234"
]
},
"userName2": {
"salary": [
"4321"
]
}
}
}
My code looks like this atm:
export const userDataControllerMixin = {
data() {
return {
userObj: {},
};
},
methods: {
NewUser(user, salary) {
this.user = user;
this.salary = salary;
user = {
salary,
};
},
// GETTING INPUT FROM USERS DIALOGBOX
getInput(inputName, inputSalary) {
const userName = document.querySelector(inputName).value;
const userSalary = document.querySelector(inputSalary).value;
const userData = new this.NewUser(userName, userSalary);
console.log(userData);
},
The structur i get is wrong, it looks like this:
NewUser {user: "asd ", salary: "123"}
When you use the word this, it means the current father, in your case NewUser
To get the variable the way you want, you need to do this:
NewUser(user, salary) {
this[user] = {
'salary':salary
};
},
In VueJS there is no need for querySelectors, since inputs are binded with v-model
Check out: https://v2.vuejs.org/v2/guide/forms.html
Because of that, we can reduce the app to one function, that reads the username and salary properties and adds them to the userObj.
I've made a working example here: https://codepen.io/bergur/pen/agZwQL?editors=1011
new Vue({
el: '#app',
data() {
return {
username: '',
salary: '',
userObj: {}
}
},
methods: {
newUser() {
this.userObj[this.username] = {
salary: [Number(this.salary)]
}
console.log(this.userObj)
}
}
})

ES6 class default value with array

everything works fine until it gets to the 2nd occurrence of contact.
TypeError: Cannot set property 'name' of undefined
Because the contact default in constructor has only 1 occurrence. Any way to work around it?
class Cust {
constructor(custData) {
this.address = {
countryCode: null,
address1: null,
address2: null,
city: null,
countrySubDivision: null,
postalCode: null
},
this.groupId = null;
this.contact = [{ name: null, phone: null }];
//this.contact.phone = custData.contact.phone
this.state = this._getState(custData.status || null);
this._setData(custData);
}
_getState(status) {
let state = (status == 'active' ? 'good' : 'bad');
return state;
}
_setData(data, prefix, index) {
let result;
for (let key in data) {
let value = data[key];
let valueIsNullOrEmpty = !value;
if (!valueIsNullOrEmpty && typeof value === 'object') {
if (Array.isArray(value)) {
value = value
.map((subProperty, index) => this._setData(subProperty, key, index))
.filter((subProperty) => Object.keys(subProperty).length > 0);
valueIsNullOrEmpty = value.length === 0;
continue;
} else {
value = this._setData(value, key);
valueIsNullOrEmpty = Object.keys(value).length === 0;
continue;
}
}
if (prefix) {
if (index >= 0) {
this[prefix][index][key] = data[key];
}
else {
this[prefix][key] = data[key];
}
}
else {
this[key] = data[key]
}
result = data[key];
}
console.log(JSON.stringify(this));
return result;
}
}
var custData = {
id: 1,
name: "Barr",
// groupId: 2,
status: "active",
address: {
countryCode: "USA",
address1: "123 main street",
address2: null,
city: "Chicago",
postalCode: "85001"
}, contact: [
{
phone: "222-222-2222"
},
{
name: "Tim"
}]
}
var cust = new Cust(custData);
You are recursively formatting the data, but you always try to change the mutated data from this, e.g.
this[key]
That will work for depth 1, but for a depth of let's say 5 it gets complicated:
this[key1][key2][key3][key4][key5]
you get the point (and thats where your code actually fails, accessing a property of a nested object with a depth greater than 2).
this will never work. Instead pass the object to modify into the method (which can be a function then), you could also keep it immutable then by returning a new object (that will make debugging easier):
function format(obj) {
const result = {};
//...
return result;
}
Then you can easily call format with nested objects.
From inside the class that can be called as:
Object.assign(this, format(data));

Transforming an object with a javascript class

Iā€™m transforming data I receive from an API. The frontend requires some calculations to be displayed.
What is the proper way to handle the data transformation?
Should I be defining a property to the object being passed? If so, why
Is this a good use case to use setters and getters or would that be unnecessary?
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
class Dog {
constructor(data) {
//do I need to set this.dog to the data object, what's the benefit of doing so?
this.dog = data;
this.name = this.dog.dog_name;
// vs
this.name = data.dog_name;
//Should I use setters and getters?
this.color = this.dog.coat_color;
// vs
this._color = this.dog.coat_color;
this.age = this.calculateAge();
}
calculateAge() {
return Date.now().getTime() - this.dog.born_time;
}
//Is this a good case where I should using getters to access the properties or would that be superfluous?
//should I be setting the properties with setters in this case?
get color() {
return this._color;
}
}
const dog = new Dog(dogData)
Your don't need to make a copy of data into your class.
You can assign the class fields directly (using object destructuring to be more readable).
const data = {
dog_name: 'filo',
born_time: 1530983852,
coat_color: 'brown'
}
class Dog {
// Directly assign values
constructor({ dog_name, born_time, coat_color }) {
this.name = dog_name
this.bornAt = born_time
this.color = coat_color
}
// Getter for computed properties
get age() {
return Date.now() - this.bornAt
}
}
const dog = new Dog(data)
Getters are needed only for computed property (dynamic or formatted values).
Good exemple:
class Person {
constructor({ firstname, lastname }) {
this.firstname = firstname
this.lastname = lastname
}
get fullname() {
return `${this.firstname} ${this.lastname}`
}
}
class Dog {
constructor(data) {
const {
dog_name: name,
born_time: age,
coat_color: color
} = data;
Object.assign(this, {
name,
age,
color
});
}
}
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
const dog = new Dog(dogData);
console.log(dog.name);
Q:
Shall I nevertheless throw in a possible read only approach? ā€“ Peter Seliger
A:
It wouldn't hurt. I appreciate the different approaches. ā€“ Matthew Moran
... here we go ...
// module start ... e.g. file: "Dog.js"
// locally scoped helper function
function calculateAge(dateOfBirth) {
return (Date.now() - dateOfBirth);
}
/*export default */class Dog {
constructor(initialValue) {
Object.defineProperties(this, {
valueOf: { // just in order to hint what `initialValue` might still be good for.
value: function () {
return Object.assign({}, initialValue);
}
},
name: {
value: initialValue.dog_name,
enumerable: true
},
color: {
value: initialValue.coat_color,
enumerable: true
},
age: {
get() {
return calculateAge(initialValue.born_time);
},
enumerable: true,
}
});
}
}
// module end.
// test
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
const dog = new Dog(dogData);
console.log('Object.keys(dog) : ', Object.keys(dog));
console.log('dog.valueOf() : ', dog.valueOf());
console.log('dog.age : ', dog.age);
console.log('dog.name : ', dog.name);
console.log('dog.color : ', dog.color);
console.log('(dog.age = 298146912) : ', (dog.age = 298146912) && dog.age);
console.log('(dog.name = "spot") : ', (dog.name = "spot") && dog.name);
console.log('(dog.color = "black") : ', (dog.color = "black") && dog.color);
.as-console-wrapper { max-height: 100%!important; top: 0; }

shallow copy not updating in nested JSON object javascript

I have an nested JSON object like this:
var jsonObj =
{ "level1" :
{ "status" : true,
"level2" : {} // and it has the same format and can go to level3, 4, etc
}
}
What I want to do is simple, I want to get to Level2, and add a new Level3 object to it.
Basically I want to do the following code below, but since the levels are dynamic, I need a function that traverse my object.
obj.Level1.Level2.Level3 = { 'status' : true}
Here's my snippet of code:
function updateStatusForLevel(nestedObj, categoryHierarchy){
// categoryHierarchy that is passed = ['Level1', 'Level2', 'Level3'];
var obj = nestedObj;
angular.forEach(categoryHierarchy, function(value, key){
obj = obj[value];
if (key === categoryHierarchy.length - 1 && angular.isUndefined(obj)){
obj[value] = {}; // I want to add 'Level3' = {}
}
});
obj.status = 'true'; // and finally, update the status
console.info("my original obj is " + JSON.stringify(nestedObj));
}
However seems like I'm missing something. If I do this, my original nestedObj is still the same as what I'm passing in (it's not updated, only the obj object is updated. I believe this should be a really simple code that traverse a nested JSON object. Why is the shallow copy not updating the original object?
Maybe like this
function updateStatusForLevel(nestedObj, categoryHierarchy){
// categoryHierarchy that is passed = ['Level1', 'Level2', 'Level3'];
if(categoryHierarchy.length) {
var shifted = categoryHierarchy.shift();
nestedObj[shifted] = {status: true};
return updateStatusForLevel(starter[shifted], categoryHierarchy);
} else {
return nestedObj;
}
}
Then calling updateStatusForLevel(nestedObj, ['level1', 'level2', 'level3']) will modify nestedObj as
level1: Object
level2: Object
level3: Object
status: true
status: true
status: true
note, this answer is not clever, so better have plnkr or something for better asnwer, but for now, try this in browser dev console
Since you only want to add a value from a certain path of nested objects, then how about creating a generic function that can do this, instead of creating a custom one. I have created a factory named helper, which can be a collection of helper functions that you may want to add later on.
DEMO
JAVASCRIPT
.factory('helper', function() {
var helper = {};
helper.set = function(object, path, value) {
// save reference of an object
var reference = object,
// last key n the path
lastKey;
path = angular.isArray(path)? path: // set as an array if it is an array
angular.isString(path)? path.split('.'): // split the path as an array if it is a string
false; // set to false and do nothing if neither of the conditions above satisfies
// check if path is truthy
if(path) {
// get the last key of the path
lastKey = path.pop();
// reduce the references until all the remaining keys
reference = path.reduce(function(reference, key) {
// check if the current object reference is undefined
if(angular.isUndefined(reference[key])) {
// set current object reference as an object if it is undefined
reference[key] = {};
}
// return the current object reference for the next iteration
return reference[key];
}, reference);
// set the last object reference for the value
reference[lastKey] = value;
}
return object;
};
return helper;
})
.run(function(helper) {
var object1 = {},
object2 = {},
object3 = {},
object4 = {
"level1" : {
"status" : true,
"level2" : {}
}
};
helper.set(object1, 'z.k.v.q', { status: false });
// object1 = { z: { k: { v: { q: { status: false } } } } }
console.log(object1);
helper.set(object2, 'a.e.i.o.u', { status: true });
// object2 = { a: { e: { i: { o: { u: { status: true } } } } } }
console.log(object2);
helper.set(object3, ['hello', 'world'], { status: undefined });
// object3 = { hello: { world: { status: undefined } } }
console.log(object3);
helper.set(object4, 'level1.level2.level3', { status: true });
// object4 = { status: true, level1: { level2: { level3: { status: true } } } }
console.log(object4);
});
Alternatively, you can use lodash for this, and you'd be able to do more object, array and collection manipulation. The lodash function you should be looking for would be _.set()
DEMO
JAVASCRIPT
.service('_', function($window) {
// you can add mixins here
// read more about lodash if you
// want to customize data manipulation
return $window._;
})
.run(function(_) {
var object1 = {},
object2 = {},
object3 = {},
object4 = {
"level1" : {
"status" : true,
"level2" : {}
}
};
_.set(object1, 'z.k.v.q', { status: false });
// object1 = { z: { k: { v: { q: { status: false } } } } }
console.log(object1);
_.set(object2, 'a.e.i.o.u', { status: true });
// object2 = { a: { e: { i: { o: { u: { status: true } } } } } }
console.log(object2);
_.set(object3, ['hello', 'world'], { status: undefined });
// object3 = { hello: { world: { status: undefined } } }
console.log(object3);
_.set(object4, 'level1.level2.level3', { status: true });
// object4 = { status: true, level1: { level2: { level3: { status: true } } } }
console.log(object4);
});

Categories