forked from Clever/underscore.deep
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathunderscore.deep.coffee
168 lines (148 loc) · 6.07 KB
/
underscore.deep.coffee
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
_.mixin(
deepKeys: deepKeys = (obj) ->
throw new Error "deepKeys must be called on an object, not '#{obj}'" unless isPlainObject obj
# In the base case where obj is empty, _.map(obj, ...) will produce []
_.flatten _.map obj, (v, k) ->
if isPlainObject(v) and not _.isEmpty(v)
_.map deepKeys(v), (subkey) -> "#{k}.#{subkey}"
else
[k]
# http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript
# This is mostly the same as the accepted answer, just shorter.
# However, whereas they convert instances of String, etc. to the literal, we create a new instance
# (`return type object for type` -> `return new type object for type`). This also allows us to
# handle Dates in the same statement.
# TODO: Support all core objects in js: Array, Boolean, Date, Function, Math, Number, RegExp, and
# String. Currently missing Function, Math, and RegExp.
deepClone: deepClone = (object) ->
return object if not object?
# normalizing primitives e.g. if someone did new String('aaa'), or new Number('444');
return new type object for type in [Date, Number, String, Boolean] when object instanceof type
# for arrays clone each element of it
return _(object).map deepClone if _(object).isArray()
# primitives like numbers, strings and stuff
return object if not _(object).isObject()
# if this is a DOM element
return object.cloneNode true if object.nodeType and _(object.cloneNode).isFunction()
# a class of some sort - we can't handle this, so we just return it
return object unless object.constructor is {}.constructor
# it is an object literal
mapValues object, deepClone
# like "of" operator except can recurse on keys with dot notation
deepHas: (obj, keys) ->
helper = (obj, keys) ->
if (keys.length is 0) or (not _.isObject obj)
false
else if keys.length is 1
_.first(keys) of obj
else
helper obj[_.first(keys)], _.rest(keys)
helper obj, if _.isArray keys then keys else keys.split('.')
deepOmit: (obj, keys) ->
unless isPlainObject obj
throw new Error "deepOmit must be called on an object, not '#{obj}'"
deepOmitOne = (obj, key) ->
helper = (obj, key_arr) ->
switch
when _.isEmpty key_arr then obj
when key_arr.length is 1 then _.omit obj, _.first key_arr
when not isPlainObject obj[_.first key_arr] then obj
else
_.extend {}, obj, _.object [_.first key_arr], [
helper obj[_.first key_arr], _.rest key_arr
]
helper obj, key.split('.')
_.reduce keys, deepOmitOne, obj
deepPick: do ->
deepGet = (obj, key) ->
helper = (obj, key_arr) ->
if key_arr.length is 1
obj?[_.first key_arr]
else
helper obj[_.first key_arr], _.rest key_arr
helper obj, key.split('.')
(obj, keys) ->
unless isPlainObject obj
throw new Error "deepPick must be called on an object, not '#{obj}'"
flat_new_obj = _.reduce keys, (new_obj, key) ->
val = deepGet obj, key
new_obj[key] = val if val isnt undefined
new_obj
, {}
deepFromFlat flat_new_obj
deepDelete: deepDelete = (obj, key) ->
return if not key? or not obj?
key = key.split '.' if not _(key).isArray()
if key.length is 1
delete obj[key]
return
deepDelete obj[key[0]], key.slice(1, key.length)
# Like _.extend, except instead of overwriting all nested objects, it extends
# each leaf of `obj` with the value at the corresponding leaf of `ext`.
# Note: this function is pure - it returns a new object instead of mutating
# the original object. If you must have a mutative version, pass true as the
# third `mutate` parameter.
deepExtend: deepExtend = (obj, ext, mutate) ->
_.reduce ext, (acc, val, key) ->
acc[key] =
if (key of obj) and isPlainObject(obj[key]) and isPlainObject(val)
then deepExtend obj[key], val
else val
acc
, if mutate then obj else _.clone obj
isPlainObject: isPlainObject = (value) -> value?.constructor is {}.constructor
deepToFlat: (obj) ->
res = {}
recurse = (obj, current) ->
for key of obj
value = obj[key]
newKey = ((if current then current + "." + key else key)) # joined key with dot
if value and isPlainObject value
recurse value, newKey # it's a nested object, so do it again
else
res[newKey] = value # it's not an object, so set the property
recurse obj
res
# Takes an object with keys with dot.notation and deepens it into dot{notation:{}}
deepFromFlat: deepFromFlat = (o) ->
oo = {}
t = undefined
parts = undefined
part = undefined
for k of o
t = oo
parts = k.split(".")
key = parts.pop()
while parts.length
part = parts.shift()
t = t[part] = t[part] or {}
t[key] = o[k]
oo
# Takes an object and replaces each of its values with the result of a
# function applied to that value (and its key).
mapValues: mapValues = (obj, f_val) ->
unless isPlainObject obj
throw new Error "mapValues must be called on an object, not '#{obj}'"
_.object _.keys(obj), _.map(obj, f_val)
deepMapValues: deepMapValues = (obj, f) ->
unless isPlainObject obj
throw new Error "deepMapValues must be called on an object, not '#{obj}'"
mapValues obj, (v, k) ->
if isPlainObject v
deepMapValues v, (subv, subk) ->
f subv, "#{k}.#{subk}"
else
f v, k
# note that the function takes a key and optionally a value, not the usual
# mapping function pattern of taking a value and optionally a key
mapKeys: mapKeys = (obj, f_val) ->
unless isPlainObject obj
throw new Error "mapKeys must be called on an object, not '#{obj}'"
_.object _.map(obj, (v,k) -> f_val k,v), _.values obj
deepPickValue: deepPickValue = (object, props) ->
if _.isString(props)
props = props.split('.')
while props.length > 0 and _.isObject(object)
object = object[props.shift()]
if props.length == 0 then object else undefined
)