UNPKG

8.66 kBJavaScriptView Raw
1
2const cloneDeep = require("lodash.clonedeep")
3
4class Model {
5
6 constructor(json) {
7
8 this.json = null
9
10 if(json) {
11 this.fromJSON(cloneDeep(json))
12 }
13
14 this.setDefaultFields()
15 this.exportProperties()
16
17 this.afterConstructor()
18 }
19
20 afterConstructor() {}
21
22 exportProperties() {
23
24 if(this.getDefaultFields().wrapper) {
25 return
26 }
27
28 if(!this.json) {
29 this.json = {}
30 }
31
32 const fields = this.getDefaultFields()
33
34 const me = this
35 Object.keys(fields).forEach(function(field) {
36 Object.defineProperty(me, field, {
37 get: function() {
38 return me.json[field]
39 },
40 set: function(value) {
41 me.json[field] = value
42 },
43 })
44 })
45 }
46
47 setDefaultFields() {
48
49 const fields = this.getDefaultFields()
50
51 if(fields.wrapper) {
52 if(this.json !== undefined || this.json !== null) {
53 return
54 }
55 switch (fields.type) {
56 case Array:
57 this.json = []
58 break
59 case Object:
60 this.json = {}
61 break
62 }
63 return
64 }
65
66 this.json = this.json || {}
67
68 Object.keys(fields).forEach((fieldName) => {
69
70 const fieldValue = fields[fieldName]
71
72 if(this.json[fieldName] !== undefined) {
73 return
74 }
75
76 if(fieldValue.default) {
77 if(typeof fieldValue.default === "function") {
78 this.json[fieldName] = fieldValue.default()
79 } else {
80 this.json[fieldName] = fieldValue.default
81 }
82 }
83 else {
84 switch (fieldValue.type) {
85 case Object:
86 this.json[fieldName] = {}
87 break
88 case Array:
89 this.json[fieldName] = []
90 break
91 default:
92 }
93 }
94 })
95 }
96
97 defaultFields() {
98 return {}
99 }
100
101 getDefaultFields() {
102
103 if(this.__fields) {
104 return this.__fields
105 }
106
107 this.__fields = this.defaultFields()
108
109 const normalizeTypeDef = (fieldName, fieldValue) => {
110
111 const isString = typeof fieldValue === "string"
112
113 if(fieldValue.name || isString) {
114 fieldValue = {
115 type: fieldValue
116 }
117 if(this.__fields.wrapper) {
118 this.__fields = fieldValue
119 } else {
120 this.__fields[fieldName] = fieldValue
121 }
122 }
123
124 if (isString) {
125 switch (fieldValue.type) {
126 case "string":
127 fieldValue.type = String
128 break
129 case "boolean":
130 fieldValue.type = Boolean
131 break
132 case "number":
133 fieldValue.type = Number
134 break
135 case "array":
136 fieldValue.type = Array
137 break
138 default:
139 fieldValue.type = Object
140 break
141 }
142 }
143
144 if(fieldValue.required === undefined) {
145 fieldValue.required = false
146 }
147
148 }
149
150 // wrapper means the object itself is a list like Array or Object
151 // @see Result class
152 if(this.__fields.wrapper) {
153 normalizeTypeDef(null, this.__fields)
154 return
155 }
156
157 Object.keys(this.__fields).forEach((fieldName) => {
158 let fieldValue = this.__fields[fieldName]
159 normalizeTypeDef(fieldName, fieldValue)
160 })
161
162 return this.__fields
163 }
164
165 validate() {
166
167 const fields = this.getDefaultFields()
168
169 const checkValueType = (type, val) => {
170 return (val instanceof type)
171 }
172
173 const checkInnerValueType = (fieldName, fieldValue, value) => {
174 if(!checkValueType(fieldValue.type, value)) {
175 throw new Error(`Field ${fieldName} must be of type ${fieldValue.type.name}`)
176 }
177 const isArray = (value instanceof Array)
178 const list = isArray ? value : Object.keys(value)
179 list.forEach((valuekey, i) => {
180 valuekey = isArray ? i : valuekey
181 if(!checkValueType(fieldValue.type.listOf, value[valuekey])) {
182 throw new Error(`Field ${fieldName}.${valuekey} must be of type ${fieldValue.type.name}`)
183 }
184 })
185 }
186
187 if(fields.wrapper) {
188 checkInnerValueType(fields.type || fields.listOf, fields, this.json)
189 return
190 }
191
192 Object.keys(fields).forEach((fieldName) => {
193
194 let fieldValue = fields[fieldName]
195 const value = this.json[fieldName]
196
197 if(fieldValue.required) {
198 if(value === null || value === undefined || value === "") {
199 throw new Error(`Field ${fieldName} is required`)
200 }
201 }
202
203 if(fieldValue.type.listOf) {
204 checkInnerValueType(fieldName, fieldValue, value)
205 }
206 else if(!checkValueType(fieldValue.type, value)) {
207 throw new Error(`Field ${fieldName} must be of type ${fieldValue.type.name}`)
208 }
209
210 })
211
212 }
213
214 typeCast(field, fieldValue) {
215
216 const Type = fieldValue.listOf
217
218 const isArray = this.json[field] instanceof Array
219
220 const list = isArray ? [] : {}
221 const values = isArray ? this.json[field] : Object.keys(this.json[field])
222
223 values.forEach((key, i) => {
224
225 key = isArray ? i : key
226 let val = this.json[field][key]
227
228 if(fieldValue.transform && typeof fieldValue.transform === "function") {
229 val = fieldValue.transform(val, key)
230 }
231
232 list[key] = new Type(val)
233 })
234
235 this.json[field] = list
236 }
237
238 fromJSON(json) {
239
240 if(typeof json === "string") {
241 json = JSON.parse(json)
242 }
243
244 this.json = Object.assign({}, json || {})
245
246 const fields = this.getDefaultFields()
247 Object.keys(fields).forEach((fieldName) => {
248
249 const fieldValue = fields[fieldName]
250
251 if(this.json[fieldName] === undefined) {
252 return
253 }
254
255 if(this.json[fieldName] === null) {
256 delete this.json[fieldName]
257 }
258
259 if(fieldValue.listOf && (
260 this.json[fieldName] instanceof Array ||
261 this.json[fieldName] instanceof Object
262 )) {
263 this.typeCast(fieldName, fieldValue)
264 }
265 else {
266 if(fieldValue.transform && typeof fieldValue.transform === "function") {
267 this.json[fieldName] = fieldValue.transform(this.json[fieldName])
268 }
269 }
270
271 })
272
273 }
274
275 toJSON(validate) {
276
277 if(validate === true) {
278 this.validate()
279 }
280
281 const model = this.json
282 const json = {}
283 const fields = this.getDefaultFields()
284 Object.keys(fields).forEach((fieldName) => {
285
286 if (model[fieldName] == undefined || model[fieldName] == null)
287 return
288
289 let raw = model[fieldName]
290 if (raw instanceof Array) {
291 raw.map((r) => r.toJSON ? r.toJSON() : r)
292 json[fieldName] = cloneDeep(raw)
293 }
294 else if (fields[fieldName].listOf) {
295 Object.keys(raw).forEach((key) => {
296 raw[key] = raw[key].toJSON ? raw[key].toJSON() : raw[key]
297 })
298 json[fieldName] = cloneDeep(raw)
299 } else {
300 raw = raw.toJSON ? raw.toJSON(validate) : raw
301 json[fieldName] = cloneDeep(raw)
302 }
303 })
304
305 return json
306 }
307
308 toString() {
309 return JSON.stringify(this.toJSON())
310 }
311
312 arrayToObject(field, keyField, json) {
313 if(json[field] && json[field] instanceof Array) {
314 const list = {}
315 json[field].forEach((val) => {
316 const key = val[keyField] || val
317 list[key] = val
318 })
319 json[field] = list
320 }
321 }
322
323}
324
325module.exports = Model