1 |
|
2 | const cloneDeep = require("lodash.clonedeep")
|
3 |
|
4 | class 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 |
|
151 |
|
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 |
|
325 | module.exports = Model
|