ES6 class default value with array - javascript

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));

Related

How to validate deeply nested object structure

I have defined object with nested properties. I want to create a validator function which will check if another object has the same structure and value type as the one that I have defined!
The is the definition of the object:
const OBJECT_SCHEMA = {
name: String,
data: [{
isSelected: Boolean,
mId: String,
mSummary: String,
mMarkets: Array,
mBdd: String,
mReplaceDict: Object,
omId: String,
omnSummary: String,
omnMarkets: Array,
omnBdd: String,
omnReplaceDict: {
id: String,
text: String,
},
}],
metadata: {
emails: Array,
description: String,
},
};
And here is the function that I have for validation. Currently it works only with one nested level! I want it to validate with many nested levels.
function validateObjectStructure(schema, obj) {
let valid = true;
firstLevel: for(const k in schema) {
if(schema[k].constructor === Array) { // if prop is of type array
let i;
for(i = 0; i < schema[k].length; i++) {
for(const kk in schema[k][i]) {
if(!obj[k][i].hasOwnProperty(kk) || obj[k][i][kk].constructor !== schema[k][i][kk]) {
valid = false;
break firstLevel;
}
}
}
}
else if(schema[k].constructor === Object) { // if prop is of type object
for(const kk in schema[k]) {
if(!obj[k].hasOwnProperty(kk) || obj[k][kk].constructor !== schema[k][kk]) {
valid = false;
break firstLevel;
}
}
}
else { // if prop is simple type
if(!obj.hasOwnProperty(k) || obj[k].constructor !== schema[k]) {
valid = false;
break;
}
}
}
return valid;
}
Do you need to work with nested levels of the obj? If yes, you can do something like this instead of the last line:
Object.values(obj).reduce((accValid, value) => {
if (typeof value === 'object') {
return accValid && validateObjectStructure(schema, value);
}
return accValid;
}, valid);
return valid;
Here's a possible implementation:
function validate(obj, schema, path = '') {
let ok = true;
if (!obj)
ok = obj === schema;
else if (typeof schema === 'function')
ok = obj.constructor === schema;
else if (typeof obj !== 'object')
ok = obj === schema;
else if (Array.isArray(schema))
ok = Array.isArray(obj) && obj.every((x, k) => validate(x, schema[0], path + '[' + k + ']'));
else {
let ko = Object.keys(obj);
let ks = Object.keys(schema);
ok = ko.length === ks.length && ks.every(k => validate(obj[k], schema[k], path + '.' + k));
}
if (!ok)
throw new Error('FAILED ' + path);
return true;
}
// example:
const OBJECT_SCHEMA = {
name: String,
data: [{
isSelected: Boolean,
mId: String,
omnReplaceDict: {
id: String,
text: {
deepObj: {
deepProp: [Number]
}
},
},
}],
};
const obj = {
name: "foo",
data: [{
isSelected: true,
mId: "bar",
omnReplaceDict: {
id: "foo",
text: {
deepObj: {
deepProp: [1, 2, "???", 3]
}
},
},
}]
};
validate(obj, OBJECT_SCHEMA)
Note: although this home-made type checker appears to work correctly, it's quite limited (e.g. how to express "array of string-number pairs" or "either null or some object"?), so it might be an option to employ a real one, like Typescript. See here for a possible implementation.

Set value in array to key in object reduce/return value inside object

The following should put out "Yuval"
console.log(pathFind(["book", "author", "name"], {
book: {
author: {
name: "Yuval"
}
}
}));
I've tried writing this function but it keeps returning undefined:
function pathFind(path, object) {
return path.reduce((accumulator, name) => {
if(accumulator && accumulator[name] != typeof 'object') {
accumulator[name]
} else {
undefined, object
}
})
}
What am I missing? (a typo?)
Is there a way to use recursion inside this function in combination with reduce? (i.e how to approach recursion for this?)
Try the following:
var obj = {
book: {
author: {
name: "Yuval"
}
}
};
var path = ["book", "author", "name"];
function findPath(obj, path){
if(path.length === 0) return obj;
return findPath(obj[path[0]], path.slice(1));
}
console.log(findPath(obj,path));

Writing a recursive javascript algorithm to find a specific object type

Essentially, I'm testing a hierarcy treeview dataset to see if there exists any object which is considered a Host node; meaning, it simply has properties HostID and HostName.
If I find just one Host node, I return true - and I'm done.
However, I'm not getting an accurate true/false return value in this recursive routine below. i.e. I get an inaccurate false return values sometimes.
hasChildHosts_2(tree: any) {
if (tree.subs !== null && tree.subs.length > 0) {
for (var i = 0; i < tree.subs.length; i++) {
if (tree.subs[i].HostID != null && tree.subs[i].HostName != null) {
return true;
}
if (tree.subs[i].subs !== undefined) {
this.hasChildHosts_2(tree.subs[i]);
}
}
return false;
} else {
return false;
}
}
a small sample set as follows: a Location node contains a subs array - which contains a Location node and two Host nodes. And of course I always know a Location by the LocationName prop and a Host by the HostID prop :
{ "UID":2,"GUID":"","LocationName":"Bergen County","ParentLocation":null, "subs":[ {"UID":42,"GUID":"","LocationName":"yy","Description":"","subs":[ {"UID":3,"GUID":"","LocationName":"Essex County","ParentLocation":null} {"HostID":100,"HostName":"MYHOST100","HostIP":"10.1.1.12"},
{"HostID":200,"HostName":"MYHOST200","HostIP":"10.1.1.19"} ] ] } }
Please review for accuracy. Feedback is appreciated.
I've implemented your code on a sample codepen. As pointed on comments, you need to return the result of the recursive function. Furthemore, the tree is an invalid JSON, I've fixed it:
var globalTree = {
UID: 2,
GUID: "",
LocationName: "Bergen County",
ParentLocation: null,
subs: [{
UID: 42,
GUID: "",
LocationName: "yy",
Description: "",
subs: [{
UID: 3,
GUID: "",
LocationName: "Essex County",
ParentLocation: null
},
{
HostID: 100,
HostName: "MYHOST100",
HostIP: "10.1.1.12"
},
{
HostID: 200,
HostName: "MYHOST200",
HostIP: "10.1.1.19"
}
]
}]
};
var hasChildHosts_2 = function(tree) {
if (tree.subs !== null && tree.subs.length > 0) {
for (var i = 0; i < tree.subs.length; i++) {
if (tree.subs[i].HostID != null && tree.subs[i].HostName != null) {
return true;
}
if (tree.subs[i].subs !== undefined) {
return hasChildHosts_2(tree.subs[i]);
}
}
return false;
} else {
return false;
}
};
console.log("result: " + hasChildHosts_2(globalTree));
Can't you simply check each item and recurse on subs if it's an array:
let obj = {"UID":2,"GUID":"","LocationName":"Bergen County","ParentLocation":null,"subs":[{"UID":42,"GUID":"","LocationName":"yy","Description":"","subs":[{"UID":3,"GUID":"","LocationName":"Essex County","ParentLocation":null},{"HostID":100,"HostName":"MYHOST00","HostIP":"10.1.1.12"},{"HostID":200,"HostName":"MYHOST00","HostIP":"10.1.1.19"}]}]}
let obj_no_host = {"UID":2,"GUID":"","LocationName":"Bergen County","ParentLocation":null,"subs":[{"UID":42,"GUID":"","LocationName":"yy","Description":"","subs":[{"UID":3,"GUID":"","LocationName":"Essex County","ParentLocation":null},{"HostID":100},{"HostID":200}]}]}
function test(o){
for (item of o) {
if (item.HostName !== undefined && item.HostIP !== undefined) return true
if (Array.isArray(item.subs)) return test(item.subs)
}
return false
}
console.log("With host -- ", test([obj]))
console.log("Without host -- ", test([obj_no_host]))

check the existence of a key in associative arrays

Tell me, how correctly to check the existence of a key in associative arrays?
For example:
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: ''
value2" '',
},
},
subkey2: '';
},
}
if ((mydata.key2 != undefined) && (mydata.key2.subkey1 != undefined) && (mydata.key2.subkey1.subkey1_1 != undefined))
mydata.key2.subkey1.subkey1_1.value1 = 'test';
Too long and confusing
((mydata.key2 != undefined) && (mydata.key2.subkey1 != undefined) && (mydata.key2.subkey1.subkey1_1 != undefined))
I would like to use a simpler function, like
safeSet(mydata.key2.subkey1.subkey1_1.value1, 'test');
or
if (is_undefined(mydata.key2.subkey1.subkey1_1.value1) == true)
mydata.key2.subkey1.subkey1_1.value1 = 'test'; // now - error if 'mydata.key2.subkey1.subkey1_1' not exist
You can create custom function using reduce() to test if nested property exists. You can just pass key as string.
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: '',
value2: '',
},
},
subkey2: ''
},
}
function safeSet(key, data) {
return key.split('.').reduce(function(r, e) {
return r ? r[e] : undefined;
}, data) != undefined
}
console.log(safeSet('key2.subkey1.subkey1_1.value1', mydata))
You should use the in operator:
"key" in obj // true, regardless of the actual value
Or, if you want to particularly test for properties of the object instance (and not inherited properties), use hasOwnProperty:
obj.hasOwnProperty("key") // true
hope this would help you.
Source: http://www.advancesharp.com/questions/628/checking-if-an-associative-array-key-exists-in-javascript
Alternatively, you can make use of the .has() method of Lodash.
Then, you would only need to check:
if (_.has(mydata, 'key2.subkey1.subkey1_1.value1')
mydata.key2.subkey1.subkey1_1.value1 = 'test';
For trying to get something in a nested structure I'd do something like this:
function getPath(element, path) {
var handledSoFar = [];
for (var i = 0; i < path.length; i++) {
var property = path[i];
handledSoFar.push(property);
if (typeof element[property] === 'undefined') {
throw new Error('Path ' + handledSoFar.join('->') + ' is undefined');
}
element = object[property];
}
return element;
}
var mydata = {
key1: '',
key2: {
subkey1: {
subkey1_1: {
value1: '',
value2: 'hi'
}
},
subkey2: ''
}
};
// Prints 'hi'
console.log(getPath(mydata, ['key2', 'subkey1', 'subkey1_1', 'value2']));
// Throws error 'Path key2->subkey2->subkey1_1 is undefined'
console.log(getPath(mydata, ['key2', 'subkey1', 'subkey1_1', 'value2']));
Of course keeping track of the search in handledSoFar is optional but might be useful for development / debugging.
You can also use the lodash deep field selector: lodash.get (documentation)
const get = require('lodash.get');
const set = require('lodash.set');
if (!get(mydata, 'key2.subkey1.subkey1_1.value1')) {
set(mydata, 'key2.subkey1.subkey1_1.value1', 'test');
}
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
var fullPath = path.split('.'),
way = fullPath.slice(),
last = way.pop();
way.reduce(function (r, a) {
return r[a] = r[a] || {};
}, object)[last] = value;
}
var object = { key1: '', key2: { subkey1: { subkey1_1: { value1: '', value2: '' } }, subkey2: '' } };
setValue(object, 'key2.subkey1.subkey1_1.value1', 'test');
console.log(object);
The problem with the example function that you proposed:
safeSet(mydata.key2.subkey1.subkey1_1.value1, 'test');
or
is_undefined(mydata.key2.subkey1.subkey1_1.value1)
Is that the mydata.key2.subkey1... part is run before the function is called. So if one of the subkeys does not exist, an exception will be thrown before your code is reached.
You could get something similar using a callback though...
safeSet(function(val) { mydata.key2.subkey1.subkey1_1.value1 = val; }, 'test')
the implementation of safeSet would then be:
var safeSet = function(setterFunc, val) {
try {
setterFunc(val);
} catch (e) {
if (e instanceof TypeError) {
return false;
} else {
throw e;
}
}
return true;
}
safeSet returns true if the value was set, and false otherwise.

Get exact diff in an Angular $watch function when watched value is an object

Controller:
$scope.$watch('userData', function(val, oldVal){
if(!val){
return;
}
console.log(filtersService.lastChange(val, oldVal))
}, true);
Service:
.service('filtersService', function() {
var _lastChange = {};
var api = {
lastChange: function(val, oldVal, nested){
var __filters = ['filters', 'weights'];
if(angular.equals(val, oldVal)){
return;
}
for(var i in val){
if(!nested){
if(__filters.indexOf(i) == -1){
continue;
}
_lastChange = {};
}
if(typeof val[i] == 'object'){
if(!angular.equals(val[i], oldVal[i])){
if(!nested){
_lastChange[i] = api.lastChange(val[i], oldVal[i], true);
}
else{
_lastChange[i] = angular.copy(val[i]);
}
}
}
else if(val[i] != oldVal[i]){
var __obj = {}
__obj[i] = val[i];
return __obj;
}
}
return _lastChange;
}
};
return api;
});
I have a $watcher that watches an object that represents form data that can be changed by the user.
Naturally, oldVal differs from val only by one element, usually primitive.
the form data consists of 3-levels nesting, so to determine the diff I use recursion, In order to allow the user to reset all of the filters except of his last change (handy feature for a scenario where there are no results).
example of the data:
val = {
age: 50,
filters: {
brands: "a_naturals",
flavors: "Caramel",
ingredients: null
},
gender: "male",
weight: 50
}
oldVal = {
age: 50,
filters: {
brands: "naturals",
flavors: "Caramel",
ingredients: "sugar"
},
gender: "male",
weight: 50
}
I would expect the returned value to be {filters: {ingredients: null}}, but it does not work properly, and the recursion stop condition is not reached for some reason.
It doesn't work for 3-levels nesting either.
It does work when one of the primitive values, or simple nested values are changed to non-null values (typeof null === 'object' issue?).

Categories