1 | import * as _ from 'lodash';
|
2 | import { CLASSNAME } from 'typescript-class-helpers/browser/classname';
|
3 | import { SYMBOL } from 'typescript-class-helpers/browser/symbols';
|
4 | import { Helpers } from './helpers';
|
5 | import { walk } from 'lodash-walk-object/browser';
|
6 | import { CLASS } from 'typescript-class-helpers/browser';
|
7 | import { Helpers as HelpersLog } from 'ng2-logger/browser';
|
8 | export var Mapping;
|
9 | (function (Mapping) {
|
10 | function decode(json, autodetect = false) {
|
11 | HelpersLog.simulateBrowser = true;
|
12 |
|
13 | if (_.isUndefined(json)) {
|
14 | return undefined;
|
15 | }
|
16 | let mapping = decodeFromDecorator(_.isArray(json) ? _.first(json) : json, !autodetect);
|
17 | if (autodetect) {
|
18 | mapping = _.merge(getMappingNaive(json), mapping);
|
19 | }
|
20 | HelpersLog.simulateBrowser = false;
|
21 | return mapping;
|
22 | }
|
23 | Mapping.decode = decode;
|
24 | function encode(json, mapping, circular = []) {
|
25 | if (_.isString(json) || _.isBoolean(json) || _.isNumber(json)) {
|
26 | return json;
|
27 | }
|
28 | if (mapping['']) {
|
29 | const decoratorMapping = getModelsMapping(CLASSNAME.getClassBy(mapping['']));
|
30 | mapping = _.merge(mapping, decoratorMapping);
|
31 | }
|
32 | let res;
|
33 | if (_.isArray(circular) && circular.length > 0) {
|
34 | res = setMappingCirc(json, mapping, circular);
|
35 | }
|
36 | else {
|
37 | res = setMapping(json, mapping);
|
38 | }
|
39 | return res;
|
40 | }
|
41 | Mapping.encode = encode;
|
42 | function decodeFromDecorator(json, production = false) {
|
43 | const entityClass = CLASS.getFromObject(json);
|
44 | const mappings = getModelsMapping(entityClass);
|
45 | return mappings;
|
46 | }
|
47 | function getModelsMapping(entity) {
|
48 | if (!_.isFunction(entity) || entity === Object) {
|
49 | return {};
|
50 | }
|
51 | const className = CLASSNAME.getClassName(entity);
|
52 |
|
53 | let enityOWnMapping = _.isArray(entity[SYMBOL.MODELS_MAPPING]) ?
|
54 | entity[SYMBOL.MODELS_MAPPING] : [{ '': className }];
|
55 | let res = {};
|
56 | let parents = enityOWnMapping
|
57 | .filter(m => !_.isUndefined(m['']) && m[''] !== className)
|
58 | .map(m => m['']);
|
59 | enityOWnMapping.reverse().forEach(m => {
|
60 | m = _.cloneDeep(m);
|
61 |
|
62 | Object.keys(m).forEach(key => {
|
63 | const v = m[key];
|
64 | const isArr = _.isArray(v);
|
65 | const model = isArr ? _.first(v) : v;
|
66 | if (parents.includes(model)) {
|
67 | m[key] = isArr ? [className] : className;
|
68 | }
|
69 | });
|
70 | res = _.merge(res, m);
|
71 | });
|
72 | res[''] = className;
|
73 |
|
74 | return res;
|
75 | }
|
76 | Mapping.getModelsMapping = getModelsMapping;
|
77 | function add(o, path, mapping = {}) {
|
78 | if (!o || Array.isArray(o) || typeof o !== 'object')
|
79 | return;
|
80 | const objectClassName = CLASSNAME.getClassName(Object.getPrototypeOf(o).constructor);
|
81 | const resolveClass = CLASSNAME.getClassBy(objectClassName);
|
82 | if (!resolveClass) {
|
83 | if (objectClassName !== 'Object') {
|
84 | if (Helpers.isBrowser) {
|
85 | console.error(`Cannot resolve class: ${objectClassName} while mapping.`);
|
86 | }
|
87 | }
|
88 | return;
|
89 | }
|
90 | if (!mapping[path])
|
91 | mapping[path] = CLASSNAME.getClassName(resolveClass);
|
92 | ;
|
93 | }
|
94 | |
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | function getMappingNaive(c, path = '', mapping = {}, level = 0) {
|
102 | if (Array.isArray(c)) {
|
103 | c.forEach(c => getMappingNaive(c, path, mapping, level));
|
104 | return mapping;
|
105 | }
|
106 | if (++level === 16)
|
107 | return;
|
108 | add(c, path, mapping);
|
109 | for (var p in c) {
|
110 | if (c.hasOwnProperty(p)) {
|
111 | const v = c[p];
|
112 | if (Array.isArray(v) && v.length > 0) {
|
113 | v.forEach((elem, i) => {
|
114 |
|
115 | const currentPaht = [path, p].filter(c => c.trim() != '').join('.');
|
116 | getMappingNaive(elem, currentPaht, mapping, level);
|
117 | });
|
118 | }
|
119 | else if (typeof v === 'object') {
|
120 | const currentPaht = [path, p].filter(c => c.trim() != '').join('.');
|
121 | add(v, currentPaht, mapping);
|
122 | getMappingNaive(v, currentPaht, mapping, level);
|
123 | }
|
124 | }
|
125 | }
|
126 | return mapping;
|
127 | }
|
128 | function getMappingPathFrom(pathLodhas) {
|
129 | if (!_.isString(pathLodhas)) {
|
130 | return void 0;
|
131 | }
|
132 | const regex = /\[([0-9a-zA-Z]|\'|\")*\]/g;
|
133 | pathLodhas = pathLodhas
|
134 | .replace(regex, '')
|
135 | .replace('..', '.');
|
136 | if (pathLodhas.startsWith('.')) {
|
137 | pathLodhas = pathLodhas.slice(1);
|
138 | }
|
139 | return pathLodhas;
|
140 | }
|
141 | function setMappingCirc(json, mapping = {}, circular = []) {
|
142 | const mainClassFn = !_.isArray(json) && CLASS.getBy(mapping['']);
|
143 |
|
144 | walk.Object(json, (v, lodashPath, changeValue) => {
|
145 | if (!_.isUndefined(v) && !_.isNull(v)) {
|
146 | const mappingPath = getMappingPathFrom(lodashPath);
|
147 | if (!_.isUndefined(mapping[mappingPath])) {
|
148 | const isArray = _.isArray(mapping[mappingPath]);
|
149 | if (!isArray) {
|
150 | const className = isArray ? _.first(mapping[mappingPath]) : mapping[mappingPath];
|
151 | const classFN = CLASS.getBy(className);
|
152 | if (_.isFunction(classFN)) {
|
153 |
|
154 | changeValue(_.merge(new classFN(), v));
|
155 | }
|
156 | }
|
157 | }
|
158 | }
|
159 | });
|
160 | circular.forEach(c => {
|
161 | const ref = _.get(json, c.circuralTargetPath);
|
162 | _.set(json, c.pathToObj, ref);
|
163 | });
|
164 | if (_.isFunction(mainClassFn)) {
|
165 | json = _.merge(new mainClassFn(), json);
|
166 | }
|
167 | return json;
|
168 | }
|
169 | function setMapping(json, mapping = {}) {
|
170 |
|
171 | if (Array.isArray(json)) {
|
172 | return json.map(j => {
|
173 | return setMapping(j, mapping);
|
174 | });
|
175 | }
|
176 | const mainClassFn = CLASS.getBy(mapping['']);
|
177 | for (const key in json) {
|
178 | if (json.hasOwnProperty(key)) {
|
179 |
|
180 |
|
181 |
|
182 | if (_.isArray(json[key])) {
|
183 | json[key] = json[key].map(arrObj => {
|
184 | const objMapping = getModelsMapping(CLASS.getBy(mapping[key]));
|
185 | return setMapping(arrObj, objMapping);
|
186 | });
|
187 | }
|
188 | else if (_.isObject(json[key])) {
|
189 | const objMapping = getModelsMapping(CLASS.getBy(mapping[key]));
|
190 | json[key] = setMapping(json[key], objMapping);
|
191 | }
|
192 | }
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | }
|
199 | Object
|
200 | .keys(mapping)
|
201 | .filter(key => key !== '' && key.split('.').length >= 2)
|
202 | .forEach(lodasPath => {
|
203 |
|
204 | const objMapping = getModelsMapping(CLASS.getBy(mapping[lodasPath]));
|
205 | const input = _.get(json, lodasPath);
|
206 | if (!_.isUndefined(input)) {
|
207 | const res = setMapping(input, objMapping);
|
208 | _.set(json, lodasPath, res);
|
209 | }
|
210 | });
|
211 | if (!mainClassFn) {
|
212 | return json;
|
213 | }
|
214 | return _.merge(new mainClassFn(), json);
|
215 | }
|
216 | function DefaultModelWithMapping(defaultModelValues, mapping) {
|
217 | return function (target) {
|
218 | if (!_.isArray(target[SYMBOL.MODELS_MAPPING])) {
|
219 | target[SYMBOL.MODELS_MAPPING] = [];
|
220 | }
|
221 | target[SYMBOL.MODELS_MAPPING].push({ '': CLASSNAME.getClassName(target) });
|
222 | if (_.isObject(mapping)) {
|
223 | target[SYMBOL.MODELS_MAPPING] = target[SYMBOL.MODELS_MAPPING].concat(mapping);
|
224 | Object.keys(mapping)
|
225 | .forEach(key => {
|
226 | const v = mapping;
|
227 | if (_.isUndefined(v) || _.isFunction(v)) {
|
228 | throw `
|
229 |
|
230 |
|
231 | Class: '${target.name}'
|
232 | [ng2rest] Bad mapping value for path: ${key} , please use type: <string> or [<string>]
|
233 | `;
|
234 | }
|
235 | });
|
236 | }
|
237 | if (_.isObject(defaultModelValues)) {
|
238 | const toMerge = {};
|
239 | const describedTarget = CLASS.describeProperites(target);
|
240 |
|
241 | describedTarget.forEach(propDefInConstr => {
|
242 | if (defaultModelValues[propDefInConstr]) {
|
243 | console.warn(`
|
244 |
|
245 | CONFLICT: default value for property: "${propDefInConstr}"
|
246 | in class "${target.name}" already defined as typescript
|
247 | default class proprty value.
|
248 |
|
249 | `);
|
250 | }
|
251 | else {
|
252 | toMerge[propDefInConstr] = null;
|
253 | }
|
254 | });
|
255 |
|
256 | target[SYMBOL.DEFAULT_MODEL] = _.merge(toMerge, defaultModelValues);
|
257 | _.merge(target.prototype, target[SYMBOL.DEFAULT_MODEL]);
|
258 |
|
259 | }
|
260 | };
|
261 | }
|
262 | Mapping.DefaultModelWithMapping = DefaultModelWithMapping;
|
263 | })(Mapping || (Mapping = {}));
|
264 |
|
\ | No newline at end of file |