One option is to create a "Model" class that has a data property which stores all values assigned to it. Use a get/set/unset API to modify the data properties. Use a parse method to get a plain object representation of the model. This allows you to create nested models.
If you are creating an application I recommend creating an API rather than accessing properties directly on the data model. The syntax may seem a little bit longer, but in the long run if you need to perform data transformations it will be easier to add methods to the class. As it stands with your initial approach, if you want to add other methods to the class then you risk overwriting them with actual data. Better to have a data property that stores all the values. Otherwise, you would not necessarily need to create a class, you could just use a plain object as you are doing.
https://jsfiddle.net/cnu6ryab/
Class
const Model = class {
constructor() {}
get data() {
if(!this._data) this._data = {}
return this._data
}
setDataProperty(key, value) {
if(!this.data[key]) {
Object.defineProperties(this.data, {
['_'.concat(key)]: {
configurable: true,
writable: true,
enumerable: false,
},
[key]: {
configurable: true,
enumerable: true,
get() { return this['_'.concat(key)] },
set(value) { this['_'.concat(key)] = value }
},
})
}
this.data[key] = value
return this
}
unsetDataProperty(key) {
if(this.data[key]) {
delete this.data[key]
}
return this
}
get() {
if(arguments[0]) return this.data[arguments[0]]
return Object.entries(this.data)
.reduce((_data, [key, value]) => {
_data[key] = value
return _data
}, {})
}
set() {
if(arguments.length === 2) {
this.setDataProperty(arguments[0], arguments[1])
} else if(
arguments.length === 1 &&
!Array.isArray(arguments[0]) &&
typeof arguments[0] === 'object'
) {
Object.entries(arguments[0]).forEach(([key, value]) => {
this.setDataProperty(key, value)
})
}
return this
}
unset() {
if(arguments[0]) {
this.unsetDataProperty(arguments[0])
} else {
Object.keys(this.data).forEach((key) => {
this.unsetDataProperty(key)
})
}
return this
}
parse(data = this.data) {
return Object.entries(data).reduce((_data, [key, value]) => {
if(value instanceof Model) {
_data[key] = value.parse()
} else {
_data[key] = value
}
return _data
}, {})
}
}
Usage
let model = new Model()
// Add a property
model.set('a', 1)
console.log(model.get())
/*
{
"a": 1
}
*/
// Add multiple properties
model.set({
'b': 2,
'c': { a: 1, b: 2, }
})
console.log(model.get())
/*
{
"a": 1,
"b": 2,
"c": {
"a": 1,
"b": 2
}
}
*/
// Replace a property
model.set({
a: ['meh', 'heh',]
})
console.log(model.get())
/*
{
"a": [
"meh",
"heh"
],
"b": 2,
"c": {
"a": 1,
"b": 2
}
}
*/
// Remove a single properties
model.unset('a')
console.log(model.get())
/*
{
"b": 2,
"c": {
"a": 1,
"b": 2
}
}
*/
// Remove all properties
model.unset()
console.log(model.get())
/*
{}
*/
// Add another property
model.set('a', 2354234)
// Create a nested model
let nestedModel = new Model()
nestedModel.set('nestedValue', 35234534254)
model.set('nestedModel', nestedModel)
console.log(model.parse())
/*
{
"a": 2354234,
"nestedModel": {
"nestedValue": 35234534254
}
}
*/
// Update the nested model
model.get('nestedModel').set('someNewValue', 'asdfasgaerwkjghak')
console.log(model.parse())
/*
{
"a": 2354234,
"nestedModel": {
"nestedValue": 35234534254,
"someNewValue": "asdfasgaerwkjghak"
}
}
*/