-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
109 lines (86 loc) · 2.74 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
function id(_) {
return _
}
const DEFAULTS = {
types: Object.create(null),
objDelimiter: '.',
mapDelimiter: ':',
preFilters: [],
postFilters: []
}
const compose = (...fns) => (i, ...args) =>
fns.reduceRight((a, n) => n(a, ...args), i)
const createRootObj = n =>
isNaN(n) ? Object.create(null) : new Array(Number(n))
const normalizeField = delimiter => m =>
m.indexOf(delimiter) > -1 ? m.split(delimiter) : [m, m]
const getMapSpec = delimiter => {
const normalizer = normalizeField(delimiter)
return mapping => {
if (!Array.isArray(mapping)) return normalizer(mapping)
return mapping.reduce(
(spec, field) => {
const [source, target] = normalizer(field)
return [[...spec[0], source], target]
},
[[], null]
)
}
}
const normalizeMapping = mapping =>
typeof mapping === 'string' ? { field: mapping } : mapping
const getMappingFilter = (type, types) => {
if (Array.isArray(type)) return compose(...type)
if (typeof type === 'function') return type
if (Object.prototype.hasOwnProperty.call(types, type)) return types[type]
return id
}
const get = (key, delimiter = DEFAULTS.objDelimiter) => {
if (key == null) return id
const spec = key.split(delimiter)
return obj => spec.reduce((a, k) => (a ? a[k] : undefined), obj)
}
const assign = (key, delimiter = DEFAULTS.objDelimiter) => {
if (key == null) return id
const spec = key.split(delimiter)
return (obj, value) => {
spec.reduce((accum, key, i, array) => {
if (i === array.length - 1) accum[key] = value
else if (!accum[key]) accum[key] = createRootObj(array[i + 1])
return accum[key]
}, obj)
return obj
}
}
class Mapper {
constructor(options) {
this.config = Object.assign(Object.create(null), DEFAULTS, options)
this.getMapSpec = getMapSpec(this.config.mapDelimiter)
this.mapFn = compose(
...this.config.postFilters,
(v, m, con, c, a) =>
getMappingFilter(m.type, this.config.types)(v, m, con, c, a),
...this.config.preFilters
)
}
map(mappings, curr, next = Object.create(null)) {
return mappings.map(normalizeMapping).reduce((accum, mapping) => {
const [sourceField, targetField] = this.getMapSpec(mapping.field)
const getter = field => get(field, this.config.objDelimiter)(curr)
let value = Array.isArray(sourceField)
? sourceField.map(getter)
: getter(sourceField)
value = this.mapFn(value, mapping, this.config, curr, accum)
if (value === undefined) {
return accum
}
return assign(targetField, this.config.objDelimiter)(accum, value)
}, next)
}
}
/* Static methods */
Mapper.get = get
Mapper.assign = assign
Mapper.compose = compose
exports.default = Mapper
module.exports = Mapper