UNPKG

27.2 kBMarkdownView Raw
1# scour.js
2
3<!-- {.massive-header.-with-tagline} -->
4
5> Traverse objects and arrays immutably
6
7Scour is a general-purpose library for dealing with JSON trees.<br>
8As a simple utility with a broad purpose, it can be used to solve many problems. Use it to:
9
10- Manage your [Redux] datastore.
11- Provide a model layer to access data in your single-page app. [](#models)
12- Navigate a large JSON tree easily.
13- Rejoice in having a lightweight alternative to [Immutable.js]. ([Compare](docs/comparison.md))
14
15[![Status](https://travis-ci.org/rstacruz/scour.svg?branch=master)](https://travis-ci.org/rstacruz/scour "See test builds")
16
17## Install
18
19```sh
20npm install --save-exact scourjs
21```
22
23```js
24window.scour // non commonjs
25const scour = require('scourjs') // commonjs/node
26import scour from 'scourjs' // es6 modules
27```
28
29## Features
30
31Calling `scour(object)` returns a wrapper that you can use to traverse `object`.
32Use [get()](#get) to retrieve values.
33
34```js
35data =
36 { users:
37 { 1: { name: 'john' },
38 2: { name: 'shane', confirmed: true },
39 3: { name: 'barry', confirmed: true } } }
40```
41
42```js
43scour(data).get('users', '1', 'name') // => 'john'
44```
45
46<br>
47
48### Traversal
49Use [go()](#go) to dig into the structure. It will return another `scour`
50wrapper scoped to that object.
51
52```js
53data =
54 { users:
55 { admins:
56 { bob: { logged_in: true },
57 sue: { logged_in: false } } } }
58```
59
60```js
61users = scour(data).go('users') // => [scour (admins)]
62admins = scour(data).go('users', 'admins') // => [scour (bob, sue)]
63
64admins.go('bob').get('logged_in') // => true
65```
66
67<br>
68
69### Chaining
70
71`scour()` provides a wrapper that can be used to chain methods. This is inspired by [Underscore] and [Lodash].
72
73```js
74scour(data)
75 .go('users')
76 .filter({ admin: true })
77 .value
78```
79
80[Underscore]: http://underscorejs.org/
81[Lodash]: http://lodash.com/
82
83<br>
84
85### Immutable modifications
86
87Use [set()](#set) to update values. Scout treats all data as immutable, so this
88doesn't modify your original `data`, but gets you a new one with the
89modifications made.
90
91```js
92data = scour(data)
93 .set(['users', '1', 'updated_at'], +new Date())
94 .value
95
96// => { users:
97// { 1: { name: 'john', updated_at: 1450667171188 },
98// 2: { name: 'shane', confirmed: true },
99// 3: { name: 'barry', confirmed: true } } }
100```
101
102<br>
103
104### Advanced traversing
105
106Use [filter()] to filter results with advanced querying.
107
108```js
109users = scour(data).go('users')
110
111users
112 .filter({ confirmed: true })
113 .at(0)
114 .get('name') // => 'shane'
115```
116
117<br>
118
119### Models
120
121Use [use()](#use) to add your own methods to certain keypaths. This makes them behave like models.<br>
122See [a detailed example](docs/extensions_example.md) to learn more.
123
124##### Sample data
125
126<!-- {.file-heading} -->
127
128```js
129data =
130 { artists:
131 { 1: { first_name: 'Louie', last_name: 'Armstrong' },
132 2: { first_name: 'Miles', last_name: 'Davis' } } }
133```
134
135##### Your models
136
137<!-- {.file-heading} -->
138
139```js
140Root = {
141 artists () { return this.go('artists') }
142}
143
144Artist = {
145 fullname () {
146 return this.get('first_name') + ' ' + this.get('last_name')
147 }
148}
149```
150
151##### Using with scour
152
153<!-- {.file-heading} -->
154
155```js
156db = scour(data)
157 .use({
158 '': Root,
159 'artists.*': Artist
160 })
161
162db.artists().find({ name: 'Miles' }).fullname()
163//=> 'Miles Davis'
164```
165
166<br>
167
168## API
169
170<!--api-->
171
172### scour
173
174> `scour(object)`
175
176Returns a scour instance wrapping `object`.
177
178```js
179scour(obj)
180```
181
182It can be called on any Object or Array. (In fact, it can be called on
183anything, but is only generally useful for Objects and Arrays.)
184
185```js
186data = { menu: { visible: true, position: 'left' } }
187scour(data).get('menu.visible')
188
189list = [ { id: 2 }, { id: 5 }, { id: 12 } ]
190scour(list).get('0.id')
191```
192
193__Chaining__:
194You can use it to start method chains. In fact, the intended use is to keep
195your root [scour] object around, and chain from this.
196
197```js
198db = scour({ menu: { visible: true, position: 'left' } })
199
200// Elsewhere:
201menu = db.go('menu')
202menu.get('visible')
203```
204
205__Properties__:
206It the [root], [value] and [keypath] properties.
207
208```js
209s = scour(obj)
210s.root // => [scour object]
211s.value // => raw data (that is, `obj`)
212s.keypath // => string array
213```
214
215__Accessing the value:__
216You can access the raw data using [value].
217
218```js
219db = scour(data)
220db.value // => same as `data`
221db.go('users').value // => same as `data.users`
222```
223
224## Chaining methods
225
226These methods are used to traverse nested structures. All these
227methods return [scour] instances, making them suitable for chaining.
228
229#### On null values
230Note that `undefined`, `false` and `null` values are still [scour]-wrapped
231when returned from [go()], [at()] and [find()].
232
233```js
234list = [ { name: 'Homer' }, { name: 'Bart' } ]
235
236scour(list).at(4) // => [ scour undefined ]
237scour(list).at(4).value // => undefined
238```
239
240This is done so that you can chain methods safely even when something is null.
241This behavior is consistent with what you'd expect with jQuery.
242
243```js
244data = { users: { ... } }
245db = scour(data)
246
247db.go('blogposts').map((post) => post.get('title'))
248// => []
249```
250
251### go
252
253> `go(keypath...)`
254
255Navigates down to a given `keypath`. Always returns a [scour] instance.
256Rules [on null values] apply.
257
258```js
259data =
260 { users:
261 { 12: { name: 'steve', last: 'jobs' },
262 23: { name: 'bill', last: 'gates' } } }
263
264scour(data).go('users') // => [scour (users)]
265scour(data).go('users', '12') // => [scour (name, last)]
266scour(data).go('users', '12').get('name') // => 'steve'
267```
268
269__Dot notation:__
270Keypaths can be given in dot notation or as an array. These statements are
271equivalent.
272
273```js
274scour(data).go('users.12')
275scour(data).go('users', '12')
276scour(data).go(['users', '12'])
277```
278
279__Non-objects:__
280If you use it on a non-object or non-array value, it will still be
281returned as a [scour] instance. This is not likely what you want; use
282[get()] instead.
283
284```js
285attr = scour(data).go('users', '12', 'name')
286attr // => [scour object]
287attr.value // => 'steve'
288attr.keypath // => ['users', '12', 'name']
289```
290
291### at
292
293> `at(index)`
294
295Returns the item at `index`. This differs from `go` as this searches by
296index, not by key. This returns a the raw value, unlike [getAt()]. Rules
297[on null values] apply.
298
299```js
300users =
301 { 12: { name: 'steve' },
302 23: { name: 'bill' } }
303
304scour(users).at(0) // => [scour { name: 'steve' }]
305scour(users).get(12) // => [scour { name: 'steve' }]
306```
307
308### getAt
309
310> `getAt(index)`
311
312Returns the item at `index`. This differs from `get` as this searches by
313index, not by key. This returns a the raw value, unlike [at()].
314*(Since v0.5)*
315
316```js
317users =
318 { 12: { name: 'steve' },
319 23: { name: 'bill' } }
320
321scour(users).at(0) // => [scour { name: 'steve' }]
322scour(users).getAt(0) // => { name: 'steve' }
323```
324
325### filter
326
327> `filter(conditions)`
328
329Sifts through the values and returns a set that matches given
330`conditions`. Supports simple objects, MongoDB-style
331queries, and functions.
332
333```js
334scour(data).filter({ name: 'Moe' })
335scour(data).filter({ name: { $in: ['Larry', 'Curly'] })
336scour(data).filter((item) => item.get('name') === 'Moe')
337```
338
339__Filter by object:__
340If you pass an object as a condition, `filter()` will check if that object
341coincides with the objects in the collection.
342
343```js
344scour(data).filter({ name: 'Moe' })
345```
346
347__Filter by function:__
348You may pass a function as a parameter. In this case, the `item` being
349passed to the callback will be a [scour]-wrapped object. The result
350will also be a [scour]-wrapped object, making it chainable.
351
352```js
353scour(data)
354 .filter((item, key) => +item.get('price') > 200)
355 .sortBy('price')
356 .first()
357```
358
359__Advanced queries:__
360MongoDB-style queries are supported as provided by [sift.js]. For
361reference, see [MongoDB Query Operators][query-ops].
362
363```js
364scour(products).filter({ price: { $gt: 200 })
365scour(articles).filter({ published_at: { $not: null }})
366```
367
368__Arrays or objects:__
369Both arrays and array-like objects are supported. In this example below,
370an object will be used as the input.
371
372```js
373devices =
374 { 1: { id: 1, name: 'Phone', mobile: true },
375 2: { id: 2, name: 'Tablet', mobile: true },
376 3: { id: 3, name: 'Desktop', mobile: false } }
377
378scour(devices).filter({ mobile: true }).len()
379// => 2
380```
381
382Also see [scour.filter()] for the unwrapped version.
383
384[query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
385
386### reject
387
388> `reject(conditions)`
389
390Inverse of [filter()] -- see `filter()` documentation for details.
391
392### find
393
394> `find(conditions)`
395
396Returns the first value that matches `conditions`. Supports MongoDB-style
397queries. For reference, see [MongoDB Query Operators][query-ops]. Also
398see [filter()], as this is functionally-equivalent to the first result of
399`filter()`. Rules [on null values] apply.
400
401[query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
402
403```js
404scour(data).find({ name: 'john' })
405scour(data).find({ name: { $in: ['moe', 'larry'] })
406```
407
408### first
409
410> `first()`
411
412Returns the first result as a [scour]-wrapped object. This is equivalent
413to [at(0)](#at).
414
415### last
416
417> `last()`
418
419Returns the first result as a [scour]-wrapped object. This is equivalent
420to `at(len() - 1)`: see [at()] and [len()].
421
422### sortBy
423
424> `sortBy(condition)`
425
426Sorts a collection. Returns a [scour]-wrapped object suitable for
427chaining. Like other chainable methods, this works on arrays as well as
428objects. *(Since v0.8)*
429
430```js
431data =
432 { 0: { name: 'Wilma' },
433 1: { name: 'Barney' },
434 2: { name: 'Fred' } }
435
436scour(data).sortBy('name').value
437// { 1: { name: 'Barney' },
438// 2: { name: 'Fred' },
439// 0: { name: 'Wilma' } }
440```
441
442__Conditions:__
443The given condition can be a string or a function. When it's given as a
444function, the `item` being passed is a [scour]-wrapped object, just like
445in [forEach()] (et al). These two examples below are
446functionally-equivalent.
447
448```js
449scour(data).sortBy('name')
450scour(data).sortBy((item) => item.get('name'))
451```
452
453You may also define nested keys in dot-notation:
454
455```js
456scour(data).sortBy('user.name')
457```
458
459## Reading methods
460
461For retrieving data.
462
463### get
464
465> `get(keypath...)`
466
467Returns data in a given `keypath`.
468
469```js
470data =
471 { users:
472 { 12: { name: 'steve' },
473 23: { name: 'bill' } } }
474
475scour(data).get('users') // => same as data.users
476scour(data).go('users').value // => same as data.users
477```
478
479__Dot notation:__
480Like [go()], the `keypath` can be given in dot notation.
481
482```js
483scour(data).get('books.featured.name')
484scour(data).get('books', 'featured', 'name')
485```
486
487### len
488
489> `len()`
490
491Returns the length of the object or array. For objects, it returns the
492number of keys.
493
494```js
495users =
496 { 12: { name: 'steve' },
497 23: { name: 'bill' } }
498
499names = scour(users).len() // => 2
500```
501
502### toArray
503
504> `toArray()`
505
506Returns an array. If the the value is an object, it returns the values of
507that object. If the value is an array, it returns it as is. Also aliased
508as `values()`.
509
510```js
511users =
512 { 12: { name: 'steve' },
513 23: { name: 'bill' } }
514
515names = scour(users).toArray()
516// => [ {name: 'steve'}, {name: 'bill'} ]
517```
518
519### keys
520
521> `keys()`
522
523Returns keys. If the value is an array, this returns the array's indices.
524Also see [toArray()] to retrieve the values instead.
525
526## Writing methods
527
528These are methods for modifying an object/array tree immutably.
529Note that all these functions are immutable--it will not modify existing
530data, but rather spawn new objects with the modifications done on them.
531
532### set
533
534> `set(keypath, value)`
535
536Sets values immutably. Returns a copy of the same object ([scour]-wrapped)
537with the modifications applied.
538
539```js
540data = { bob: { name: 'Bob' } }
541db = scour(data)
542db.set([ 'bob', 'name' ], 'Robert')
543// db.value == { bob: { name: 'Robert' } }
544```
545
546__Immutability:__
547This is an immutable function, and will return a new object. It won't
548modify your original object.
549
550```js
551profile = scour({ name: 'John' })
552profile2 = profile.set('email', 'john@gmail.com')
553
554profile.value // => { name: 'John' }
555profile2.value // => { name: 'John', email: 'john@gmail.com' }
556```
557
558__Using within a scope:__
559Be aware that using all writing methods ([set()], [del()], [extend()]) on
560scoped objects (ie, made with [go()]) will spawn a new [root] object. If
561you're keeping a reference to the root object, you'll need to update it
562accordingly.
563
564```js
565db = scour(data)
566book = db.go('book')
567book.root === db // correct so far
568
569book = book.set('title', 'IQ84')
570book = book.del('sale_price')
571book.root !== db // `root` has been updated
572```
573
574__Dot notation:__
575Like [go()] and [get()], the keypath can be given in dot notation or an
576array.
577
578```js
579scour(data).set('menu.left.visible', true)
580scour(data).set(['menu', 'left', 'visible'], true)
581```
582
583### del
584
585> `del(keypath)`
586
587Deletes values immutably. Returns a copy of the same object
588([scour]-wrapped) with the modifications applied.
589
590Like [set()], the keypath can be given in dot notation or an
591array.
592
593```js
594scour(data).del('menu.left.visible')
595scour(data).del(['menu', 'left', 'visible'])
596```
597
598See [set()] for more information on working with immutables.
599
600### extend
601
602> `extend(objects...)`
603
604Extends the data with more values. Returns a [scour]-wrapped object. Just
605like [Object.assign], you may pass multiple objects to the parameters.
606
607```js
608data = { a: 1, b: 2 }
609data2 = scour(data).extend({ c: 3 })
610```
611
612```js
613data2 // => [scour { a: 1, b: 2, c: 3 }]
614data2.value // => { a: 1, b: 2, c: 3 }
615```
616
617When used with anything non-object, it will be overridden.
618
619```js
620data = {}
621db = scour(data)
622db = db.go('state').extend({ pressed: true }).root
623
624db.value // => { state: { pressed: true } }
625```
626
627See [set()] for more information on working with immutables.
628
629## Utility methods
630
631For stuff.
632
633### use
634
635> `use(extensions)`
636
637Extends functionality for certain keypaths with custom methods.
638See [Extensions example] for examples.
639
640```js
641data =
642 { users:
643 { 12: { name: 'steve', surname: 'jobs' },
644 23: { name: 'bill', surname: 'gates' } } }
645
646extensions = {
647 'users.*': {
648 fullname () {
649 return this.get('name') + ' ' + this.get('surname')
650 }
651 }
652}
653
654scour(data)
655 .use(extensions)
656 .get('users', 12)
657 .fullname() // => 'bill gates'
658```
659
660__Extensions format:__
661The parameter `extension` is an object, with keys being keypath globs, and
662values being properties to be extended.
663
664```js
665.use({
666 'books.*': { ... },
667 'authors.*': { ... },
668 'publishers.*': { ... }
669 })
670```
671
672__Extending root:__
673To bind properties to the root method, use an empty string as the keypath.
674
675```js
676.use({
677 '': {
678 users() { return this.go('users') },
679 authors() { return this.go('authors') }
680 }
681})
682```
683
684__Keypath filtering:__
685You can use glob-like `*` and `**` to match parts of a keypath. A `*` will
686match any one segment, and `**` will match one or many segments. Here are
687some examples:
688
689- `users.*` - will match `users.1`, but not `users.1.photos`
690- `users.**` - will match `users.1.photos`
691- `users.*.photos` - will match `users.1.photos`
692- `**` will match anything
693
694__When using outside root:__
695Any extensions in a scoped object (ie, made with [go()]) will be used relative
696to it. For instance, if you define an extension to `admins.*` inside
697`.go('users')`, it will affect `users.
698
699```js
700data = { users: { john: { } }
701db = scour(data)
702
703users = db.go('users')
704 .use({ '*': { hasName () { return !!this.get('name') } })
705
706users.go('john').hasName() // works
707```
708
709While this is supported, it is *not* recommended: these extensions will not
710propagate back to the root, and any objects taken from the root will not
711have those extensions applied to them.
712
713```js
714users.go('john').hasName() // works
715db.go('users.john').hasName() // doesn't work
716```
717
718### index
719
720> `index(keypath, field)`
721
722Sets up indices to improve [filter()] performance. *(Since v0.12)*
723
724- `keypath` *(String | Array)* - the keypath of the collection.
725- `field` *(String)* - the name of the field to be indexed.
726
727```js
728data =
729 { users:
730 { 1: { name: 'John Creamer' },
731 2: { name: 'Stephane K' } } }
732
733db = scour(data).index('users', 'name')
734db.filter({ name: 'Stephane K' })
735```
736
737Doing this will add an index in the root (acccessible via
738`scour().indices`) to make searches faster for certain [filter()] queries.
739Any writing actions ([set()], [extend()], [del()]) will automatically
740update the index.
741
742See [scour-search] for more information on indexing.
743
744[scour-search]: https://github.com/rstacruz/scour-search
745
746### toJSON
747
748> `toJSON()`
749
750Returns the value for serialization. This allows `JSON.stringify()` to
751work with `scour`-wrapped objects. The name of this method is a bit
752confusing, as it doesn't actually return a JSON string — but I'm afraid
753that it's the way that the JavaScript API for [JSON.stringify] works.
754
755[JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior
756
757### equal
758
759> `equal(other)`
760
761Checks for equality between two Scour-wrapped objects.
762
763```js
764a = scour(data)
765b = scour(data)
766
767a.equal(b) // => true
768```
769
770## Iteration methods
771
772These methods are generally useful for collections. These
773methods can work with either arrays or array-like objects, such as
774below.
775
776```js
777subjects =
778 { 1: { id: 1, title: 'Math', level: 101 },
779 2: { id: 2, title: 'Science', level: 103 },
780 3: { id: 3, title: 'History', level: 102 } }
781```
782
783__Values:__
784For all these functions, The items passed onto the callbacks _is_ a
785[scour]-wrapped object. Use `item.value` or `this` to access the raw
786values.
787
788```js
789scour(subjects).forEach((subject, key) => {
790 console.log(subject.get('title'))
791})
792```
793
794__Return values:__
795For methods that return values (such as [map()], the returned results _is
796not_ a [scour]-wrapped object, and isn't suitable for chaining.
797
798```js
799scour(subjects).map((subject, key) => {
800 return subject.get('title') + ' ' + subject.get('level')
801})
802// => [ 'Math 101', 'Science 103', 'History 102' ]
803```
804
805### forEach
806
807> `forEach(function(item, key, index))`
808
809Loops through each item. Supports both arrays and objects.
810The rules specified in [Iteration methods] apply.
811
812```js
813users =
814 { 12: { name: 'steve' },
815 23: { name: 'bill' } }
816
817scour(users).each((user, key) => {
818 console.log(user.get('name'))
819})
820```
821
822The values passed onto the function are:
823
824- `item` - the value; always a scour object.
825- `key` - the key.
826- `index` - the index.
827
828### each
829
830> `each(fn)`
831
832Alias for [forEach](#foreach).
833
834### map
835
836> `map(function(item, key))`
837
838Loops through each item and returns an array based on the iterator's
839return values. Supports both arrays and objects.
840The rules specified in [Iteration methods] apply.
841
842```js
843users =
844 { 12: { name: 'Steve' },
845 23: { name: 'Bill' } }
846
847names = scour(users).map((user, key) => user.get('name'))
848// => [ 'Steve', 'Bill' ]
849```
850
851### mapObject
852
853> `mapObject(function(val, key))`
854
855Creates a new `Object` with with the results of calling a provided function
856on every element in this array. Works like [Array#map], but also works on
857objects as well as arrays, and it returns an object instead.
858The rules specified in [Iteration methods] apply.
859
860See [scour.mapObject()] for details and the non-wrapped version.
861
862### indexedMap
863
864> `indexedMap(function(val, key))`
865
866Creates a new `Object` with with the results of calling a provided function
867returning the keys and values for the new object.
868The rules specified in [Iteration methods] apply.
869
870See [scour.indexedMap()] for details and the non-wrapped version.
871
872### reset
873
874> `reset(value, options)`
875
876Returns a clone with the `value` replaced. The new instance will
877retain the same properties, so things like [use()] extensions are carried
878over.
879
880```js
881db = scour({ name: 'hello' })
882db.value //=> { name: 'hello' }
883
884db = db.reset({})
885db.value // => {}
886```
887
888This is useful for, say, using Scour with [Redux] and implementing an
889action to reset the state back to empty.
890
891## Attributes
892
893These attributes are available to [scour] instances.
894
895### value
896
897> `value`
898
899The raw value being wrapped. You can use this to terminate a chained call.
900
901```js
902users =
903 [ { name: 'john', admin: true },
904 { name: 'kyle', admin: false } ]
905
906scour(users)
907 .filter({ admin: true })
908 .value
909// => [ { name: 'john', admin: true } ]
910```
911
912### root
913
914> `root`
915
916A reference to the root [scour] instance.
917Everytime you traverse using [go()], a new [scour] object is spawned that's
918scoped to a keypath. Each of these [scour] objects have a `root` attribute
919that's a reference to the top-level [scour] object.
920
921```js
922db = scour(...)
923
924photos = db.go('photos')
925photos.root // => same as `db`
926```
927
928This allows you to return to the root when needed.
929
930```js
931db = scour(...)
932artist = db.go('artists', '9328')
933artist.root.go('albums').find({ artist_id: artist.get('id') })
934```
935
936### keypath
937
938> `keypath`
939
940An array of strings representing each step in how deep the current scope is
941relative to the root. Each time you traverse using [go()], a new [scour]
942object is spawned.
943
944```js
945db = scour(...)
946
947users = db.go('users')
948users.keypath // => ['users']
949
950admins = users.go('admins')
951admins.keypath // => ['users', 'admins']
952
953user = admins.go('23')
954user.keypath // => ['users', 'admins', '23']
955```
956
957## Utility functions
958
959These are utilities that don't need a wrapped object.
960
961### scour.get
962
963> `scour.get(object, keypath)`
964
965Gets a keypath from an object.
966
967```js
968data = { users: { bob: { name: 'john' } } }
969
970result = get(data, ['users', 'bob', 'name'])
971// => 'robert'
972```
973
974This is also available as `require('scourjs/utilities/get')`.
975
976### scour.set
977
978> `scour.set(object, keypath, value)`
979
980Sets a `keypath` into an `object` immutably.
981
982```js
983data = { users: { bob: { name: 'john' } } }
984
985result = set(data, ['users', 'bob', 'name'], 'robert')
986// => { users: { bob: { name: 'robert' } } }
987```
988
989This is also available as `require('scourjs/utilities/set')`.
990
991### scour.del
992
993> `scour.del(object, keypath)`
994
995Deletes a `keypath` from an `object` immutably.
996
997```js
998data = { users: { bob: { name: 'robert' } } }
999result = del(data, ['users', 'bob', 'name'])
1000
1001// => { users: { bob: {} } }
1002```
1003
1004This is also available as `require('scourjs/utilities/del')`.
1005
1006### scour.extendIn
1007
1008> `scour.extendIn(object, keypath, extensions...)`
1009
1010Extends a `keypath` from an `object` immutably.
1011
1012```js
1013data = { users: { bob: { name: 'robert' } } }
1014result = extendIn(data, ['users', 'bob'], { email: 'bob@gmail.com' })
1015
1016// => { users: { bob: { name: 'robert', email: 'bob@gmail.com' } } }
1017```
1018
1019This is also available as `require('scourjs/utilities/extend_in')`.
1020
1021### scour.each
1022
1023> `scour.each(iterable, fn)`
1024
1025Iterates through `iterable`, either an object or an array. This is an
1026implementation of [Array#forEach] that also works for objects. The callback
1027`fn` will be invoked with two parameters: `currentValue` and `key`, just
1028like `Array#forEach`.
1029
1030This is also available as `require('scourjs/utilities/each')`.
1031
1032[Array#forEach]: http://devdocs.io/javascript/global_objects/array/foreach
1033
1034### scour.map
1035
1036> `scour.map(iterable, fn)`
1037
1038Creates a new `Array` with with the results of calling a provided function
1039on every element in this array. Works like [Array#map], but also works on
1040objects as well as arrays.
1041
1042The callback `fn` will be invoked with two parameters: `currentValue` and
1043`key`, just like [Array#map].
1044
1045This is also available as `require('scourjs/utilities/map')`.
1046
1047[Array#map]: http://devdocs.io/javascript/global_objects/array/map
1048
1049### scour.mapObject
1050
1051> `scour.mapObject(iterable, fn)`
1052
1053Creates a new `Object` with with the results of calling a provided function
1054on every element in this array. Works like [Array#map], but also works on
1055objects as well as arrays, and it returns an object instead.
1056
1057The callback `fn` will be invoked with two parameters: `currentValue` and
1058`key`, just like [Array#map].
1059
1060```js
1061object = { a: 20, b: 30, c: 40 }
1062result = scour.mapObject(object, (val, key) => {
1063 return '$' + val + '.00'
1064})
1065
1066// => { a: '$20.00', b: '$30.00', c: '$40.00' }
1067```
1068
1069This is also available as `require('scourjs/utilities/map_object')`.
1070
1071### scour.indexedMap
1072
1073> `scour.indexedMap(iterable, fn)`
1074
1075Creates a new `Object` with with the results of calling a provided function
1076returning the keys and values for the new object.
1077
1078The callback `fn` will be invoked with two parameters: `currentValue` and
1079`key`, just like [Array#map].
1080
1081The callback `fn` should return an array with two elements: with `result[0]`
1082being the key, and `result[1]` being the value. These are what the new
1083object will be constructed with.
1084
1085The `iterable` parameter can be an object or an array. This works like
1086`Array#map`, but also works on objects as well as arrays.
1087
1088```js
1089list = ['Fred', 'Barney', 'Wilma']
1090
1091object = scour.indexedMap(list, (val, key) => {
1092 var newkey = val.substr(0, 1)
1093 return [ newkey, val ]
1094})
1095
1096// => { f: 'Fred', b: 'Barney', w: 'Wilma' }
1097```
1098
1099This is also available as `require('scourjs/utilities/indexed_map')`.
1100
1101### scour.filter
1102
1103> `scour.filter(iterable, function(val, key), [isArray])`
1104
1105Creates a new Array or Object with all elements that pass the test
1106implemented by the provided function.
1107
1108Works like [Array#filter], but will return an object if an object is also passed.
1109
1110The optional `isArray` argument, when passed `true`, will always make this
1111return an `Array`. If `false`, it will always be an `Object`. Leave it
1112`undefined` for the default behavior.
1113
1114This is also available as `require('scourjs/utilities/filter')`.
1115
1116[Array#filter]: http://devdocs.io/javascript/global_objects/array/filter
1117
1118### scour.sortBy
1119
1120> `scour.sortBy(iterable, criteria)`
1121
1122Sorts by a given criteria.
1123
1124```js
1125list = [ { name: 'Fred' }, { name: 'Barney' }, { name: 'Wilma' } ]
1126scour.sortBy(list, 'name')
1127```
1128
1129This is also available as `require('scourjs/utilities/sort_by')`.
1130<!--api:end-->
1131
1132[at()]: #at
1133[del()]: #del
1134[extend()]: #extend
1135[filter()]: #filter
1136[forEach()]: #foreach
1137[get()]: #get
1138[getAt()]: #getat
1139[go()]: #go
1140[keypath]: #keypath
1141[len()]: #len
1142[map()]: #map
1143[root]: #root
1144[scour]: #scour
1145[set()]: #set
1146[toArray()]: #toarray
1147[value]: #value
1148[use()]: #use
1149[scour.mapObject()]: #scour.mapobject
1150[scour.indexedMap()]: #scour.indexedmap
1151[scour.filter()]: #scour-filter
1152[Iteration methods]: #iteration-methods
1153[on null values]: #on-null-values
1154
1155[Extensions example]: docs/extensions_example.md
1156[Object.assign]: https://devdocs.io/javascript/global_objects/object/assign
1157[sift.js]: https://www.npmjs.com/package/sift
1158[Redux]: http://rackt.github.io/redux
1159[Immutable.js]: http://facebook.github.io/immutable-js/
1160[scour-search]: https://github.com/rstacruz/scour-search
1161
1162## Thanks
1163
1164**scour** © 2015+, Rico Sta. Cruz. Released under the [MIT] License.<br>
1165Authored and maintained by Rico Sta. Cruz with help from contributors ([list][contributors]).
1166
1167> [ricostacruz.com](http://ricostacruz.com) &nbsp;&middot;&nbsp;
1168> GitHub [@rstacruz](https://github.com/rstacruz) &nbsp;&middot;&nbsp;
1169> Twitter [@rstacruz](https://twitter.com/rstacruz)
1170
1171[MIT]: http://mit-license.org/
1172[contributors]: http://github.com/rstacruz/scour/contributors