Skip to content

Commit

Permalink
Generalize fromJS() and Seq() to support Sets
Browse files Browse the repository at this point in the history
- When calling `Seq(value)` on an iterable value, use the convention of the default iterator method being equal to one of the prototype methods `value.entries()` and `value.keys()` as a clear indication of Keyed or Set style collections.
  - Introduces `isEntriesIterable()` and `isKeysIterable()` to capture these patterns.
  - Uses them within `Seq()` to produce an entries sequence or set sequence.
- Use the general `Seq()` in the implementation of `fromJS()`, rather than repeating the logic of determining which Seq conversion to apply.
  - Replaces this logic with constraints on which values can be converted
  - Introduces allowing iterable objects
  - Introduces allowing array-likes
- Add `toSet()` as a possible return from the default converter of `fromJS()` in the case the generated Seq is a SetSeq.
  • Loading branch information
leebyron committed Jul 23, 2021
1 parent 6b03706 commit 36e2b5d
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 19 deletions.
10 changes: 10 additions & 0 deletions src/Iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,13 @@ function getIteratorFn(iterable) {
return iteratorFn;
}
}

export function isEntriesIterable(maybeIterable) {
const iteratorFn = getIteratorFn(maybeIterable);
return iteratorFn && iteratorFn === maybeIterable.entries;
}

export function isKeysIterable(maybeIterable) {
const iteratorFn = getIteratorFn(maybeIterable);
return iteratorFn && iteratorFn === maybeIterable.keys;
}
14 changes: 8 additions & 6 deletions src/Seq.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
hasIterator,
isIterator,
getIterator,
isEntriesIterable,
isKeysIterable,
} from './Iterator';

import hasOwnProperty from './utils/hasOwnProperty';
Expand Down Expand Up @@ -286,11 +288,7 @@ function emptySequence() {
}

export function keyedSeqFromValue(value) {
const seq = Array.isArray(value)
? new ArraySeq(value)
: hasIterator(value)
? new CollectionSeq(value)
: undefined;
const seq = maybeIndexedSeqFromValue(value);
if (seq) {
return seq.fromEntrySeq();
}
Expand All @@ -316,7 +314,11 @@ export function indexedSeqFromValue(value) {
function seqFromValue(value) {
const seq = maybeIndexedSeqFromValue(value);
if (seq) {
return seq;
return isEntriesIterable(value)
? seq.fromEntrySeq()
: isKeysIterable(value)
? seq.toSetSeq()
: seq;
}
if (typeof value === 'object') {
return new ObjectSeq(value);
Expand Down
22 changes: 13 additions & 9 deletions src/fromJS.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { KeyedSeq, IndexedSeq } from './Seq';
import { Seq } from './Seq';
import { hasIterator } from './Iterator';
import { isImmutable } from './predicates/isImmutable';
import { isIndexed } from './predicates/isIndexed';
import { isKeyed } from './predicates/isKeyed';
import isArrayLike from './utils/isArrayLike';
import isPlainObj from './utils/isPlainObj';

export function fromJS(value, converter) {
Expand All @@ -14,12 +18,11 @@ export function fromJS(value, converter) {
}

function fromJSWith(stack, converter, value, key, keyPath, parentValue) {
const toSeq = Array.isArray(value)
? IndexedSeq
: isPlainObj(value)
? KeyedSeq
: null;
if (toSeq) {
if (
typeof value !== 'string' &&
!isImmutable(value) &&
(isArrayLike(value) || hasIterator(value) || isPlainObj(value))
) {
if (~stack.indexOf(value)) {
throw new TypeError('Cannot convert circular structure to Immutable');
}
Expand All @@ -28,7 +31,7 @@ function fromJSWith(stack, converter, value, key, keyPath, parentValue) {
const converted = converter.call(
parentValue,
key,
toSeq(value).map((v, k) =>
Seq(value).map((v, k) =>
fromJSWith(stack, converter, v, k, keyPath, value)
),
keyPath && keyPath.slice()
Expand All @@ -41,5 +44,6 @@ function fromJSWith(stack, converter, value, key, keyPath, parentValue) {
}

function defaultConverter(k, v) {
return isKeyed(v) ? v.toMap() : v.toList();
// Effectively the opposite of "Collection.toSeq()"
return isIndexed(v) ? v.toList() : isKeyed(v) ? v.toMap() : v.toSet();
}
12 changes: 8 additions & 4 deletions type-definitions/immutable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4871,6 +4871,10 @@ declare namespace Immutable {
/**
* Deeply converts plain JS objects and arrays to Immutable Maps and Lists.
*
* `fromJS` will convert Arrays and [array-like objects][2] to a List, and
* plain objects (without a custom prototype) to a Map. [Iterable objects][3]
* may be converted to List, Map, or Set.
*
* If a `reviver` is optionally provided, it will be called with every
* collection as a Seq (beginning with the most nested collections
* and proceeding to the top-level collection itself), along with the key
Expand All @@ -4893,10 +4897,6 @@ declare namespace Immutable {
* }
* ```
*
* `fromJS` is conservative in its conversion. It will only convert
* arrays which pass `Array.isArray` to Lists, and only raw objects (no custom
* prototype) to Map.
*
* Accordingly, this example converts native JS data to OrderedMap and List:
*
* <!-- runkit:activate -->
Expand Down Expand Up @@ -4933,6 +4933,10 @@ declare namespace Immutable {
*
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter
* "Using the reviver parameter"
* [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#working_with_array-like_objects
* "Working with array-like objects"
* [3]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol
* "The iterable protocol"
*/
function fromJS(
jsValue: unknown,
Expand Down

0 comments on commit 36e2b5d

Please sign in to comment.