Search code examples
javascriptimmutable.js

Immutable.fromJS() is not deep


The description of Immutable.fromJS is:

Deeply converts plain JS objects and arrays to Immutable Maps and Lists.

But that's wrong. I have an object with the following structure. The capitalized items are ES6 classes.

Foo
  prop1
  prop2
  bars
    Bar
      prop1
      prop2
    Bar
      prop1
      prop2
  bazes
    Baz
      prop1
      prop2
      bars
        Bar
          prop1
          prop2
        Bar
          prop1
          prop2
    Baz
      prop1
      prop2
      bars
        Bar
          prop1
          prop2
        Bar
          prop1
          prop2

The result of Immutable.fromJS(foo) is a Map. The arrays bars and bazes are Lists. However, each element of these lists is still a plain (ES6) object. The bars property of each Baz is an array, not a list.

Am I doing something wrong, or is the documentation incorrect?

Maybe the deep feature is not supported for ES6 objects? If that's the case, how can I make my object deeply immutable?

UPDATE:

This works but feels kinda gross: Immutable.fromJS(JSON.parse(JSON.stringify(foo)))


Solution

  • The very first sentence in the docs for fromJS is:

    Deeply converts plain JS objects and arrays to Immutable Maps and Lists.

    If Foo, Bar, and Baz are ES6 classes then they are neither plain JS objects nor arrays—they're instances of a class. So, no, the docs are not incorrect.

    As an aside, if Immutable.fromJS automatically converted any object it encountered into a plain JS object, as you seem to have expected it to, that would be very surprising behavior to most users, and not at all desirable.

    But since that's the behavior you want, you'll be happy to know that the Immutable wiki has a section on this exact topic, which I'll duplicate here for posterity:

    Here is an example which will convert any Object, including exotic Objects, to Immutable.Map:

    function fromJSGreedy(js) {
      return typeof js !== 'object' || js === null ? js :
        Array.isArray(js) ? 
          Immutable.Seq(js).map(fromJSGreedy).toList() :
          Immutable.Seq(js).map(fromJSGreedy).toMap();
    }
    

    That's pretty simple. And, indeed, it works exactly as promised, as you can see by running the below snippet.

    class Foo {
      constructor(name, ...children) {
        this.name = name;
        this.children = children;
      }
    }
    
    class Bar extends Foo {}
    class Baz extends Foo {}
    
    const myBar = new Bar("myBar", new Baz("myBaz1"), new Baz("myBaz2"));
    const myFoo = new Foo("myFoo", myBar);
    
    function fromJSGreedy(js) {
      return typeof js !== 'object' || js === null ? js :
        Array.isArray(js) ? 
          Immutable.Seq(js).map(fromJSGreedy).toList() :
          Immutable.Seq(js).map(fromJSGreedy).toMap();
    }
    
    console.log(fromJSGreedy(myFoo).toString());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>