-
Notifications
You must be signed in to change notification settings - Fork 0
/
object_util.js
187 lines (160 loc) · 5.83 KB
/
object_util.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
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
'use strict';
function ObjectUtil() {
}
/**
* Some static util functions for retrieving and checking nested keys in JSON objects. Syntax inspired by Immutable. Does support Arrays.
*/
/**
* Checks whether a nested object key exists in an object. Dives into arrays if required.
* @see http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key
* @param obj The JSON object in which to check for the nested key
* @param path_components An array of strings and/or numbers defining the path (of key names) to the key in question. Numbers are interpreted as array indices.
* @returns {boolean} Whether the key exists. (The value may still be null, undefined, or falsy.)
*/
ObjectUtil.hasIn = function(obj, path_components) {
let handled_path = [];
let path_component;
let last_obj = null;
let index;
for (var i = 0; i < path_components.length; i++) {
path_component = path_components[i];
handled_path.push(path_component);
// special handling for array indices given as number instead of text
if (typeof(path_component) === "number") {
if (Array.isArray(obj)) {
index = path_component;
if (index >= 0 && index < obj.length) {
last_obj = obj;
obj = obj[index];
continue;
}
else {
return false;
}
}
}
if (typeof(obj) != 'object' || obj == null) {
return false; // must be object or array, and the latter is also of type object in JS
}
if(Array.isArray(obj)) {
if(ObjectUtil._stringRepresentsPositiveIntegerIncludingZero(path_component)) {
let index = parseInt(path_component);
if(index >= 0 && index < obj.length) {
last_obj = obj;
obj = obj[index];
}
else {
return false;
}
} else {
return false;
}
}
else {
// is object
if (!obj.hasOwnProperty(path_component)) {
return false;
}
last_obj = obj;
obj = obj[path_component];
}
}
return true;
}
ObjectUtil._stringRepresentsPositiveIntegerIncludingZero = function(str) {
var n = Math.floor(Number(str));
return String(n) === str && n >= 0;
}
/**
* Checks whether a nested object key exists and is not null or undefined.
* @param obj The JSON object in which to check for the nested key
* @param path_components An array of strings defining the path (of key names) to the key in question
* @returns {boolean} Whether the key exists and is neither undefined nor null
*/
ObjectUtil.hasDefinedAndNonNullIn = function(obj, path_components) {
if(ObjectUtil.hasIn(obj, path_components)) {
let value = ObjectUtil.getIn(obj, path_components);
let result = (typeof(value) !== 'undefined' && value != null);
return result;
}
return false;
}
/**
* Retrieves the nested obj from the given one. Note that the path has to exist, Check first with hasIn if in doubt.
* @param obj The input JSON object
* @param path_components The path to the nested object as an array of strings
* @param alternate An alternate return value that will be used if the given path does not exist in the object
* @returns {Object} The nested object
*/
ObjectUtil.getIn = function(obj, path_components, alternate) {
if(!ObjectUtil.hasIn(obj, path_components)) {
return alternate;
}
let path_component;
for (var i = 0; i < path_components.length; i++) {
path_component = path_components[i];
if(Array.isArray(obj)) {
let index = parseInt(path_component);
obj = obj[index];
} else {
obj = obj[path_component];
}
}
return obj;
}
/**
* Creates a new object based on the old one that has the given path added.
* @param obj The input JSON object
* @param path_components The path to the nested object as an array of strings and/or numbers. Numbers are treated as array indices.
If you set an array index n while n-1 does not exist, this function will push values into the array to make the index word.
You can determine what will be pushed by using the last, optional parameter of this function. If you omit it, undefined will be used.
* @param value The target value to set for the final path element.
* @param value_to_use_for_missing_array_elements A value to push into arrays to fill them to the required length, if needed. Optional, defaults to undefined.
* @returns {Object} The new JSON object, which includes the requested path.
**/
ObjectUtil.setIn = function(obj, path_components, value, value_to_use_for_missing_array_elements) {
var obj_cp = Object.assign({}, obj);
if(ObjectUtil.hasIn(obj, path_components)) {
return obj_cp;
}
var path_component_is_number = path_components.map(path_component => (typeof path_component === "number"));
// handle requested array at root
if(path_components.length > 0 && path_component_is_number[0]) {
obj_cp = [];
}
var sub_obj = obj_cp;
var path_component;
var index;
var current_path_component_is_number;
var next_path_component_is_number;
for (var i = 0; i < path_components.length; i++) {
path_component = path_components[i];
if (typeof(sub_obj[path_component]) != 'object' || sub_obj[path_component] == null) { // need to create path
next_path_component_is_number = ((i < path_components.length - 1) && path_component_is_number[i+1]);
current_path_component_is_number = path_component_is_number[i];
if(next_path_component_is_number) {
if(!Array.isArray(sub_obj[path_component])) {
sub_obj[path_component] = [];
}
}
else {
// In case of an array: we may need to add array elements before this one before we can insert it
if(current_path_component_is_number) {
if(Array.isArray(sub_obj)) {
index = path_component;
while(index >= sub_obj.length) {
sub_obj.push(value_to_use_for_missing_array_elements);
}
}
}
sub_obj[path_component] = {};
if(i === (path_components.length - 1)) {
sub_obj[path_component] = value;
}
}
}
sub_obj = sub_obj[path_component];
}
return obj_cp;
}
module.exports = ObjectUtil;