forked from burritojustice/xyz_space_invader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.js
109 lines (100 loc) · 3.83 KB
/
utils.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
export const PROP_TYPES = {
STRING: 0,
NUMERIC: 1
};
// parse a nested object in dot and array notation (e.g. feature.a[2].b.c) into a "stack" of its components:
// ['feature', 'a', 2, 'b', 'c']
export function parsePropStack(prop) {
return prop &&
prop
.replace(/\\\./g, '__DELIMITER__') // handle escaped . notation in property names
.split('.')
.flatMap(s => {
// handle array bracket syntax (only for numeric indexes), e.g. names[4]
const brackets = s.match(/\[(\d+)\]/);
if (brackets != null && brackets.length === 2) {
const idx = parseInt(brackets[1]); // the numeric index we found inside the brackets
if (typeof idx === 'number' && !isNaN(idx)) {
return [s.substr(0, brackets.index), idx]; // insert the array index
}
}
return s; // continue without array index
})
.map(s => typeof s === 'string' ? s.replace(/__DELIMITER__/g, '.') : s); // swap out delimeter
}
// format nested property name stack with dot (object) and bracket (array) notation
export function formatPropStack(propStack) {
return propStack &&
propStack
.map((p, i) => {
const n = parseInt(p);
if (typeof n === 'number' && !isNaN(n)) {
return `[${p}]`;
}
else {
return `${i > 0 ? '.' : ''}${p}`;
}
})
.join('');
}
// parse a (potentially) nested object, tracking the level and components of each property
export function parseNestedObject(obj, level = -1, prop = null, propStack = [], rows = []) {
if (Array.isArray(obj)) {
if (prop) {
rows.push({ level, obj, prop: formatPropStack(propStack), propStack }); // header row
}
obj.forEach((x, i) => parseNestedObject(x, level + 1, i, [...propStack, i], rows));
} else if (typeof obj === 'object' && obj != null) {
if (prop) {
rows.push({ level, obj, prop: formatPropStack(propStack), propStack }); // header row
}
for (const p in obj) {
parseNestedObject(obj[p], level + 1, p, [...propStack, p], rows);
}
} else {
rows.push({ level, obj, prop: formatPropStack(propStack), value: obj, propStack });
}
return rows;
}
// lookup value of a nested feature property
export function lookupProperty(properties, propStack) {
return propStack && propStack.reduce((obj, prop) => obj && obj[prop] !== undefined ? obj[prop] : null, properties);
}
// stringify an object with JSON.stringify, but include functions as strings
export function stringifyWithFunctions (obj) {
if (typeof obj === 'function') {
return obj.toString();
}
return JSON.stringify(obj, function (k, v) {
// Convert functions to strings
return (typeof v === 'function') ? v.toString() : v;
});
};
// More robust number parsing, try to get a floating point or integer value from a string
export function parseNumber(value) {
// don't bother parsing these
if (value == null || typeof value === 'object' || typeof value === 'boolean') {
return value;
}
else if (typeof value === 'number') {
return isNaN(value) ? undefined : value;
}
const n = value.replace(/[,\/]/g, '').replace(/([0-9])\-([0-9])/g, '$1$2');
// ^ strip formatter chars, including interior dashes, forward slash, e.g. '1,500' => '1500' (NB only works for US-style numbers)
const m = n.match(/[-+]?([0-9]+,?)*\.?[0-9]+/); // get floating point or integer via regex, keeping negative nums
const num = parseFloat(m && m[0]);
if (typeof num === 'number' && !isNaN(num)) {
return num;
}
}
// Can a minimum % of values in an array be parsed as numbers?
export function mostlyNumeric(values, threshold = 100) {
if(!values) {
return false;
}
const numeric = values
.map(v => parseNumber(v))
.filter(x => typeof x === 'number' && !isNaN(x) && Math.abs(x) !== Infinity)
.length;
return numeric / values.length >= (threshold / 100);
}