[6a3a178] | 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).
|
---|