README.md (10776B)
1 # jsprim: utilities for primitive JavaScript types 2 3 This module provides miscellaneous facilities for working with strings, 4 numbers, dates, and objects and arrays of these basic types. 5 6 7 ### deepCopy(obj) 8 9 Creates a deep copy of a primitive type, object, or array of primitive types. 10 11 12 ### deepEqual(obj1, obj2) 13 14 Returns whether two objects are equal. 15 16 17 ### isEmpty(obj) 18 19 Returns true if the given object has no properties and false otherwise. This 20 is O(1) (unlike `Object.keys(obj).length === 0`, which is O(N)). 21 22 ### hasKey(obj, key) 23 24 Returns true if the given object has an enumerable, non-inherited property 25 called `key`. [For information on enumerability and ownership of properties, see 26 the MDN 27 documentation.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties) 28 29 ### forEachKey(obj, callback) 30 31 Like Array.forEach, but iterates enumerable, owned properties of an object 32 rather than elements of an array. Equivalent to: 33 34 for (var key in obj) { 35 if (Object.prototype.hasOwnProperty.call(obj, key)) { 36 callback(key, obj[key]); 37 } 38 } 39 40 41 ### flattenObject(obj, depth) 42 43 Flattens an object up to a given level of nesting, returning an array of arrays 44 of length "depth + 1", where the first "depth" elements correspond to flattened 45 columns and the last element contains the remaining object . For example: 46 47 flattenObject({ 48 'I': { 49 'A': { 50 'i': { 51 'datum1': [ 1, 2 ], 52 'datum2': [ 3, 4 ] 53 }, 54 'ii': { 55 'datum1': [ 3, 4 ] 56 } 57 }, 58 'B': { 59 'i': { 60 'datum1': [ 5, 6 ] 61 }, 62 'ii': { 63 'datum1': [ 7, 8 ], 64 'datum2': [ 3, 4 ], 65 }, 66 'iii': { 67 } 68 } 69 }, 70 'II': { 71 'A': { 72 'i': { 73 'datum1': [ 1, 2 ], 74 'datum2': [ 3, 4 ] 75 } 76 } 77 } 78 }, 3) 79 80 becomes: 81 82 [ 83 [ 'I', 'A', 'i', { 'datum1': [ 1, 2 ], 'datum2': [ 3, 4 ] } ], 84 [ 'I', 'A', 'ii', { 'datum1': [ 3, 4 ] } ], 85 [ 'I', 'B', 'i', { 'datum1': [ 5, 6 ] } ], 86 [ 'I', 'B', 'ii', { 'datum1': [ 7, 8 ], 'datum2': [ 3, 4 ] } ], 87 [ 'I', 'B', 'iii', {} ], 88 [ 'II', 'A', 'i', { 'datum1': [ 1, 2 ], 'datum2': [ 3, 4 ] } ] 89 ] 90 91 This function is strict: "depth" must be a non-negative integer and "obj" must 92 be a non-null object with at least "depth" levels of nesting under all keys. 93 94 95 ### flattenIter(obj, depth, func) 96 97 This is similar to `flattenObject` except that instead of returning an array, 98 this function invokes `func(entry)` for each `entry` in the array that 99 `flattenObject` would return. `flattenIter(obj, depth, func)` is logically 100 equivalent to `flattenObject(obj, depth).forEach(func)`. Importantly, this 101 version never constructs the full array. Its memory usage is O(depth) rather 102 than O(n) (where `n` is the number of flattened elements). 103 104 There's another difference between `flattenObject` and `flattenIter` that's 105 related to the special case where `depth === 0`. In this case, `flattenObject` 106 omits the array wrapping `obj` (which is regrettable). 107 108 109 ### pluck(obj, key) 110 111 Fetch nested property "key" from object "obj", traversing objects as needed. 112 For example, `pluck(obj, "foo.bar.baz")` is roughly equivalent to 113 `obj.foo.bar.baz`, except that: 114 115 1. If traversal fails, the resulting value is undefined, and no error is 116 thrown. For example, `pluck({}, "foo.bar")` is just undefined. 117 2. If "obj" has property "key" directly (without traversing), the 118 corresponding property is returned. For example, 119 `pluck({ 'foo.bar': 1 }, 'foo.bar')` is 1, not undefined. This is also 120 true recursively, so `pluck({ 'a': { 'foo.bar': 1 } }, 'a.foo.bar')` is 121 also 1, not undefined. 122 123 124 ### randElt(array) 125 126 Returns an element from "array" selected uniformly at random. If "array" is 127 empty, throws an Error. 128 129 130 ### startsWith(str, prefix) 131 132 Returns true if the given string starts with the given prefix and false 133 otherwise. 134 135 136 ### endsWith(str, suffix) 137 138 Returns true if the given string ends with the given suffix and false 139 otherwise. 140 141 142 ### parseInteger(str, options) 143 144 Parses the contents of `str` (a string) as an integer. On success, the integer 145 value is returned (as a number). On failure, an error is **returned** describing 146 why parsing failed. 147 148 By default, leading and trailing whitespace characters are not allowed, nor are 149 trailing characters that are not part of the numeric representation. This 150 behaviour can be toggled by using the options below. The empty string (`''`) is 151 not considered valid input. If the return value cannot be precisely represented 152 as a number (i.e., is smaller than `Number.MIN_SAFE_INTEGER` or larger than 153 `Number.MAX_SAFE_INTEGER`), an error is returned. Additionally, the string 154 `'-0'` will be parsed as the integer `0`, instead of as the IEEE floating point 155 value `-0`. 156 157 This function accepts both upper and lowercase characters for digits, similar to 158 `parseInt()`, `Number()`, and [strtol(3C)](https://illumos.org/man/3C/strtol). 159 160 The following may be specified in `options`: 161 162 Option | Type | Default | Meaning 163 ------------------ | ------- | ------- | --------------------------- 164 base | number | 10 | numeric base (radix) to use, in the range 2 to 36 165 allowSign | boolean | true | whether to interpret any leading `+` (positive) and `-` (negative) characters 166 allowImprecise | boolean | false | whether to accept values that may have lost precision (past `MAX_SAFE_INTEGER` or below `MIN_SAFE_INTEGER`) 167 allowPrefix | boolean | false | whether to interpret the prefixes `0b` (base 2), `0o` (base 8), `0t` (base 10), or `0x` (base 16) 168 allowTrailing | boolean | false | whether to ignore trailing characters 169 trimWhitespace | boolean | false | whether to trim any leading or trailing whitespace/line terminators 170 leadingZeroIsOctal | boolean | false | whether a leading zero indicates octal 171 172 Note that if `base` is unspecified, and `allowPrefix` or `leadingZeroIsOctal` 173 are, then the leading characters can change the default base from 10. If `base` 174 is explicitly specified and `allowPrefix` is true, then the prefix will only be 175 accepted if it matches the specified base. `base` and `leadingZeroIsOctal` 176 cannot be used together. 177 178 **Context:** It's tricky to parse integers with JavaScript's built-in facilities 179 for several reasons: 180 181 - `parseInt()` and `Number()` by default allow the base to be specified in the 182 input string by a prefix (e.g., `0x` for hex). 183 - `parseInt()` allows trailing nonnumeric characters. 184 - `Number(str)` returns 0 when `str` is the empty string (`''`). 185 - Both functions return incorrect values when the input string represents a 186 valid integer outside the range of integers that can be represented precisely. 187 Specifically, `parseInt('9007199254740993')` returns 9007199254740992. 188 - Both functions always accept `-` and `+` signs before the digit. 189 - Some older JavaScript engines always interpret a leading 0 as indicating 190 octal, which can be surprising when parsing input from users who expect a 191 leading zero to be insignificant. 192 193 While each of these may be desirable in some contexts, there are also times when 194 none of them are wanted. `parseInteger()` grants greater control over what 195 input's permissible. 196 197 ### iso8601(date) 198 199 Converts a Date object to an ISO8601 date string of the form 200 "YYYY-MM-DDTHH:MM:SS.sssZ". This format is not customizable. 201 202 203 ### parseDateTime(str) 204 205 Parses a date expressed as a string, as either a number of milliseconds since 206 the epoch or any string format that Date accepts, giving preference to the 207 former where these two sets overlap (e.g., strings containing small numbers). 208 209 210 ### hrtimeDiff(timeA, timeB) 211 212 Given two hrtime readings (as from Node's `process.hrtime()`), where timeA is 213 later than timeB, compute the difference and return that as an hrtime. It is 214 illegal to invoke this for a pair of times where timeB is newer than timeA. 215 216 ### hrtimeAdd(timeA, timeB) 217 218 Add two hrtime intervals (as from Node's `process.hrtime()`), returning a new 219 hrtime interval array. This function does not modify either input argument. 220 221 222 ### hrtimeAccum(timeA, timeB) 223 224 Add two hrtime intervals (as from Node's `process.hrtime()`), storing the 225 result in `timeA`. This function overwrites (and returns) the first argument 226 passed in. 227 228 229 ### hrtimeNanosec(timeA), hrtimeMicrosec(timeA), hrtimeMillisec(timeA) 230 231 This suite of functions converts a hrtime interval (as from Node's 232 `process.hrtime()`) into a scalar number of nanoseconds, microseconds or 233 milliseconds. Results are truncated, as with `Math.floor()`. 234 235 236 ### validateJsonObject(schema, object) 237 238 Uses JSON validation (via JSV) to validate the given object against the given 239 schema. On success, returns null. On failure, *returns* (does not throw) a 240 useful Error object. 241 242 243 ### extraProperties(object, allowed) 244 245 Check an object for unexpected properties. Accepts the object to check, and an 246 array of allowed property name strings. If extra properties are detected, an 247 array of extra property names is returned. If no properties other than those 248 in the allowed list are present on the object, the returned array will be of 249 zero length. 250 251 ### mergeObjects(provided, overrides, defaults) 252 253 Merge properties from objects "provided", "overrides", and "defaults". The 254 intended use case is for functions that accept named arguments in an "args" 255 object, but want to provide some default values and override other values. In 256 that case, "provided" is what the caller specified, "overrides" are what the 257 function wants to override, and "defaults" contains default values. 258 259 The function starts with the values in "defaults", overrides them with the 260 values in "provided", and then overrides those with the values in "overrides". 261 For convenience, any of these objects may be falsey, in which case they will be 262 ignored. The input objects are never modified, but properties in the returned 263 object are not deep-copied. 264 265 For example: 266 267 mergeObjects(undefined, { 'objectMode': true }, { 'highWaterMark': 0 }) 268 269 returns: 270 271 { 'objectMode': true, 'highWaterMark': 0 } 272 273 For another example: 274 275 mergeObjects( 276 { 'highWaterMark': 16, 'objectMode': 7 }, /* from caller */ 277 { 'objectMode': true }, /* overrides */ 278 { 'highWaterMark': 0 }); /* default */ 279 280 returns: 281 282 { 'objectMode': true, 'highWaterMark': 16 } 283 284 285 # Contributing 286 287 See separate [contribution guidelines](CONTRIBUTING.md).