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