Imbricated json object value change - javascript

I have an empty javascript object, and I want to do something like this:
this.conflictDetails[this.instanceId][this.modelName][this.entityId] = { solved: true };
The problem is that for the modelName I get Cannot read properties of undefined.
I think it is because the empty object this.conflictDetails does not have the property that I m looking for, I wonder how I could write this to work and to be as clear as possible?
The result should look like:
{
'someInstanceId': {
'someModelName': {
'some entityId': {
solved: true;
}
}
}
}
With mu; time instance ids, that hold multiple model names, that have multiple entity Ids

check this the '?' operator
let obj = {
'someInstanceId': {
'someModelName': {
'someEntityId': {
solved: true
}
}
}
};
let value1 = obj.someInstanceId?.someModelName?.someEntityId;
console.log(value1);
// or
value1 = obj["someInstanceId"] ?? {};
value1 = value1["someModelName"] ?? {};
value1 = value1["someEntityId"];
console.log(value1);
let value2 = obj.someInstanceId?.someMissModelName?.someEntityId;
console.log(value2);

You can use interfaces to define the model of the object, or you can check if every nested object is not null nor undefined.
I would prefer the first option if I use typescript. If not, then i would stick to the not null/undefined option
First we create a model:
export interface CustomObject {
[instanceId: string]: {
[modelName: string]: {
[entityId: string]: {
solved: boolean;
};
};
};
}
Now that we have a model we can fill an object with data:
We define the object in the .ts file of your component
public dataObjects: CustomObject = {
instance1: {
basicModel: {
Entity1: {
solved: false,
},
},
}
};
Now you can have for example a function to change the solved status, I assume that there are several instanceIds, modelNames and entityIds.
public resolveObject(
instanceId: string,
modelName: string,
entity: string,
solved: boolean
) {
this.dataObjects[instanceId][modelName][entity].solved = solved;
}
Now you can call the method with the object details:
this.resolveObject('instance1', 'basicModel', 'Entity1', true);
After this line is executed the solved will changer from false to true.
I've created a StackBlitz to show you: https://stackblitz.com/edit/angular-ivy-ibfohk

Related

Is it possible to make a localStorage wrapper with TypeScript typed parameters and mapped return values?

I'm trying to construct a TS wrapper for localStorage with a TS schema that defines all the possible values in localStorage. Problem is that I can't figure out how to type the return value so that it's mapped to the approriate type from the schema: for example if I want to call LocalStorage.get("some_number"), I want the return type to be of type number. Is this even possible in TS?
Keys -typed parameter value works really well for the input!
type Values = LocalStorageSchema[Keys] returns the union type of the values which is not what I'm looking for.
Also it seems that it's not possible to use the variable key in the get function for type narrowing...
I have also looked into generic types: LocalStorage.get(...) but I think that kind of defeats the whole point of typing the return value.
Anyone got ideas for this? Thanks!
type LocalStorageSchema = {
token: string;
some_string: string;
some_number: number;
};
type Keys = keyof LocalStorageSchema;
// type Values = LocalStorageSchema[Keys]; ???
export const LocalStorage = {
get(key: Keys): any {
const data = window.localStorage.getItem(key);
//type ReturnType = ???
if (data !== null) {
return data;
}
console.error(`localStorage missing object with key ${key}`);
return null;
},
set(key: Keys, value: any) {
window.localStorage.setItem(key, value);
},
remove(key: Keys) {
window.localStorage.removeItem(key);
},
clear() {
window.localStorage.clear();
},
};
Use generics to return the right type for the key !
type LocalStorageSchema = {
token: string;
some_string: string;
some_number: number;
};
type Keys = keyof LocalStorageSchema;
export const LocalStorage = {
get<T extends Keys>(key: T): LocalStorageSchema[T] | null { // Return type will depend on the key
const data = window.localStorage.getItem(key);
//type ReturnType = ???
if (data !== null) {
return data as LocalStorageSchema[T];
}
console.error(`localStorage missing object with key ${key}`);
return null;
},
set<T extends Keys>(key: T, value: LocalStorageSchema[T]) {
window.localStorage.setItem(key, value as any);
},
remove(key: Keys) {
window.localStorage.removeItem(key);
},
clear() {
window.localStorage.clear();
},
};
const a = LocalStorage.get('token'); // string | null
Playground
You can use generic as in this link. There are still some errors left to fix. Link how to handle null values. It's up to you, so I didn't fix it.

What is this data structure with default property and type-like object in JS?

I notice that in some JS objects, they have properties with some special behaviours. For example, the model objects in Sequelize, if I log a model to the console, I see that the object contains properties such as _data, _changed, _modelOptions, etc.
However, when accessing the object itself, the _data property appears to be its default property. For instance:
const userModel = UserModel.findOne(...);
console.log(userModel.email); // this prints the email field of the record
console.log(userModel._data.dataValues.email) // this also prints the email of the record
It appears that I don't have to access the email from _data.dataValues. I can access it directly from the userModel object.
When I print the whole object, I also notice that the values in _data.dataValues get printed.
With this:
console.log(JSON.stringify(userModel))
I will get this result:
{
name: 'John',
email: 'john#smith.com'
}
But with this:
console.log(userModel)
I will get this instead:
t {
_data: user { // what is that 'user' before the object? is it a type definition?
dataValues: {
name: 'John',
email: 'john#smith.com'
}
_previousDataValues: {
name: 'John',
email: 'john#smith.com'
}
_modelOptions: {
...
}
...
}
}
This looks a little different from the usual JS object in that it seems to have a "type" of sort to the object those properties are "internal" and not visible when printed out.
I thought it was a class at first but I tried printing a class I've created and compared the output with the console output from this model and they look different.
I don't see this data structure in JS often. What is this data structure in JS and in Node specifically? What is different and useful about this "special" object compared to the regular object in JS?
Achieving these form of behaviour is nothing special. This can be done easily using Object.defineProperty or Object.defineProperties.
Here is an example
function Person(fName, lName) {
this._data = {
fName: fName,
lName: lName
}
Object.defineProperties(this, {
fName: {
get : function () {
return this._data.fName;
},
set: function (value) {
this._data.fName = value;
}
},
lName: {
get : function () {
return this._data.lName;
},
set: function (value) {
this._data.lName = value;
}
}
});
}
const ratul = new Person("Ratul", "sharker");
console.log(ratul);
console.log(ratul.fName);
ratul.fName = "Ra2l";
console.log(ratul.fName);
Here the enumerable property is by default set to false (check define properties documentation.) If you set it to true then it will appear in console.log(ratul).
Primary usage of these sort of behaviour in sequelize is to keep track of value changes. Directly from Sequelize github.
setDataValue(key, value) {
const originalValue = this._previousDataValues[key];
if (!_.isEqual(value, originalValue)) {
this.changed(key, true);
}
this.dataValues[key] = value;
}
Most obvious reason to track data value changes is to while calling Model.save, then sequelize can optimise which attributes/properties should sequelize write to db.
This is where Object.defineProperty is being used, which is declared within refreshAttributes, which is called from init.
Object.defineProperty(this.prototype, key, attributeManipulation[key]);

Why I got undefined variable?

I have class with property:
public requestLoadPersonal: Personal[] = [];
And one method:
private filterByGender(selectedValue: any): void {
console.log(this.requestLoadPersonal);
this.requestLoadPersonal = this.requestLoadPersonal.filter(
(p: Personal) => {
return selectedValue ? p.gender == selectedValue : true;
}
);
}
In constructor of class I have:
public filterFn = {
name: this.filterByGender
}
Why when I call function this.filterByGender from object filterFn by key. I got undefined message, why variable this.requestLoadPersonal is not available inside:
console.log(this.requestLoadPersonal); ?
Calling as:
this.filterFn['name']({...});
I tried to bind variable:
this.filterFn['name']({...}).bind(this.requestLoadPersonal);
In your constructor you should bind the function to this like this:
constructor() { this.filterFn = { name: this.filterByName.bind(this) }; }
Because just {name: this.filterByName} loses the this context, i.e.
filterByName has this context of some other instance.
However, I'd suggest you simplify everything and make it more straight-forward. Currently it's complex and thus, error-prone.

How to create an empty object which has defined value type

I'm trying to create an object that has string type keys and the values are objects of a class I defined.
an example would be:
let rockHardObs = {
'Lady': {
'Rah': new Gaga('rah-ah-ah-ah'),
'Roma': new Gaga('roma-ma')
}
}
So I want to just initialize it with an empty object corresponding to the key 'Lady' like this.
let rockHardObs = {
'Lady': { string : Gaga }
}
You can use an empty object literal and a type assertion to tell the compiler what the expected type of the property will be:
let rockHardObs = {
'Lady': { } as { [key: string]: Gaga }
}
let rockHardObs: {
[key?: "Lady"]: { [key: string] : Gaga }
} = {}

Issue in Typing a class with Flowjs

I have the following code that I'm attempting to type with Flow
type Metadata = {
currentPage: string,
};
type State = {
result: {
metadata: Metadata,
}
}
type EmptyObject = {};
type Test = Metadata | EmptyObject;
class HelperFn {
state: State;
metadata: Test;
constructor(state: State) {
this.state = state;
if (state && state.result && state.result.metadata) {
this.metadata = state.result.metadata;
} else {
this.metadata = {};
}
}
getCurrentPageNumber() {
return this.metadata.currentPage;
}
}
I've created Types that I'm assigning later on. In my class, I assign the type Test to metadata. Metadata can be either an object with properties or an empty object. When declaring the function getCurrentPageNumber, the linter Flow tells me that it
cannot get 'this.metadata.currentPage' because property 'currentPage' is missing in EmptyObject
Looks like Flow only refers to the emptyObject. What is the correct syntax to tell Flow that my object can either be with properties or just empty?
Since metaData can be empty, Flow is correctly telling you that this.metadata.currentPage may not exist. You could wrap it in some sort of check like
if (this.metadata.currentPage) {
return this.metadata.currentPage
} else {
return 0;
}
To get it to work properly.

Categories

Resources