npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

immutable-assign

v2.1.5

Published

Lightweight immutable helper that allows you to continue working with Plain JavaScript Objects

Downloads

16,157

Readme

immutable-assign (iassign.js)

Lightweight immutable helper that allows you to continue working with POJO (Plain Old JavaScript Object), and supports full TypeScript type checking for nested objects.

NPM version Build Status coverage status size

Sauce Test Status

This library is trying to solve the following problems:

  • Most immutable JavaScript libraries try to encapsulate the data and provide proprietary APIs to work with the data. They are more verbose than normal JavaScript syntax. E.g., map1.get('b') vs map1.b, nested2.getIn(['a', 'b', 'd']) vs nested2.a.b.d, etc.
  • Encapsulated data is no more POJO, therefore cannot be easily used with other libraries, e.g., lodash, underscore, etc.
  • Most immutable libraries leak themselves throughout your entire application (including view components), however, they should have been encapsulated at the place where updates happen (e.g., Redux reducers). This is also a pain when you need to change to another immutable library that has its own APIs.
  • seamless-immutable address some of the above issues when reading the properties, but still use verbose APIs to write properties.
  • Immutability Helpers allows us to work with POJO, but it has still introduced some magic keywords, such as $set, $push, etc.
  • In addition, we lost TypeScript type checking. E.g., when calling nested2.getIn(["a", "b", "c"]), TypeScript won't be able to warn me if I changed property "c" to "d".

This library is an alternative to Immutable.js, it has only one method iassign(), which accept a POJO object and return you a new POJO object with specific property updated. However, since it works with other libraries such as lodash (refer to example 4), it provides all the functionalities you need plus immutability.

  • I have added some options to freeze input and output using deep-freeze, which can be used in development to make sure they don't change unintentionally by us or the 3rd party libraries.

This library will leave your POJO objects completely untouched (except the optional deep-freeze), it does not wrap around nor add any methods/properties to your POJO objects.

This library works in JavaScript and it works really well with TypeScript, because of its generic type argument inference; and since you are working with POJO (not the wrapper objects), you can utilize the full power of TypeScript: IntelliSense, type checking and refactoring, etc.

Performance

Performance of this library should be comparable to Immutable.js, because read operations will always occur more than write operations. When using this library, all your react components can read object properties directly. E.g., you can use <TextBox value={this.state.userinfo.fullName}> in your components, instead of <TextBox value={this.state.getIn(["userinfo", "fullName"])}>. In addition, shouldComponentUpdate() can compare POJO objects without knowing about the immutable libraries, e.g., return this.props.userInfo.orders !== nextProps.userInfos.orders. I.e., the more read operations you have, the more it will outperform Immutable.js. Following are the benchmarks for multiple immutable libraries (assuming the read to write ratio is 5 to 1):

$ npm run benchmarks

Mutable
Total elapsed = 57 ms (read) + 41 ms (write) = 98 ms.

Immutable (immutable.js)
Total elapsed = 254 ms (read) + 847 ms (write) = 1101 ms.

Immutable (Object.assign)
Total elapsed = 66 ms (read) + 1339 ms (write) = 1405 ms.

Immutable (immutable-assign)
Total elapsed = 77 ms (read) + 2265 ms (write) = 2342 ms.

Immutable (immer setAutoFreeze(false))
Total elapsed = 65 ms (read) + 4706 ms (write) = 4771 ms.

Immutable (seamless-immutable production)
Total elapsed = 73 ms (read) + 29688 ms (write) = 29761 ms.

Full performance test results and test script can be found at benchmarks.

Install with npm

npm install immutable-assign
# or
yarn add immutable-assign

Example 1: Update 1st level object properties, it has skipped the getProp parameter

const iassign = require("immutable-assign");

// Deep freeze both input and output, can be used in development to make sure they don't change.
iassign.setOption({ freeze: true });

const map1 = { a:1, b:2, c:3 };

// 1: Calling iassign() to update map1.b, using overload 1
const map2 = iassign(
    map1,
    function (m) { m.b = 50; return m; }
);

// map2 = { a:1, b: 50, c:3 }
// map2 !== map1

Example 2: Update 1st level list/array elements, it has skipped the getProp parameter

const iassign = require("immutable-assign");

const list1 = [1, 2];


// 2.1: Calling iassign() to push items to list1, using overload 1
const list2 = iassign(
    list1,
    function (l) { l.push(3, 4, 5); return l; }
);

// list2 = [1, 2, 3, 4, 5]
// list2 !== list1


// 2.2: Calling iassign() to unshift item to list2, using overload 1
const list3 = iassign(
    list2,
    function (l) { l.unshift(0); return l; }
);

// list3 = [0, 1, 2, 3, 4, 5]
// list3 !== list2


// 2.3, Calling iassign() to concat list1, list2 and list3, using overload 1
const list4 = iassign(
    list1,
    function (l) { return l.concat(list2, list3); }
);

// list4 = [1, 2, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]
// list4 !== list1


// 2.4, Calling iassign() to concat sort list4, using overload 1
const list5 = iassign(
    list4,
    function (l) { return l.sort(); }
);

// list5 = [0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 5]
// list5 !== list4

Example 3: Update nested level object properties

const iassign = require("immutable-assign");

const nested1 = { a:{ b:{ c:[3, 4, 5] } } };


// 3.1: Calling iassign() to assign d to nested1.a.b
const nested2 = iassign(
    nested1,
    function (n) { return n.a.b; },
    function (b) { b.d = 6; return b; }
);

// nested2 = { a:{ b:{ c:[3, 4, 5], d: 6 } } }
// nested2 !== nested1


// 3.2: Calling iassign() to increment nested2.a.b.d
const nested3 = iassign(
    nested2,
    function (n) { return n.a.b.d; },
    function (d) { return d + 1; }
);

// nested3 = { a:{ b:{ c:[3, 4, 5], d: 7 } } }
// nested3 !== nested2


// 3.3: Calling iassign() to push item to nested3.a.b.c
const nested4 = iassign(
    nested3,
    function (n) { return n.a.b.c; },
    function (c) { c.push(6); return c; }
);

// nested4 = { a:{ b:{ c:[3, 4, 5, 6], d: 7 } } }
// nested4 !== nested3

Example 4: Work with 3rd party libraries, e.g., lodash

const iassign = require("immutable-assign");
const _ = require("lodash");

const nested1 = { a: { b: { c: [1, 2, 3] } } };


// 4.1: Calling iassign() and _.map() to increment to every item in "c" array
const nested2 = iassign(
    nested1,
    function (n) { return n.a.b.c; },
    function (c) {
        return _.map(c, function (i) { return i + 1; });
    }
);

// nested2 = { a: { b: { c: [2, 3, 4] } } };
// nested2 !== nested1


// 4.2: Calling iassign() and _.flatMap()
const nested3 = iassign(
    nested2,
    function (n) { return n.a.b.c; },
    function (c) {
        return _.flatMap(c, function (i) { return [i, i]; });
    }
);

// nested3 = { a: { b: { c: [2, 2, 3, 3, 4, 4] } } };
// nested3 !== nested2

Example 5: Update nested object

const iassign = require("immutable-assign");

const o1 = { a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]], c2: {} }, b2: {} }, a2: {} };

// 5: Calling iassign() to increment o1.a.b.c[0][0].d
const o2 = iassign(
    o1,
    function (o) { return o.a.b.c[0][0]; },
    function (ci) { ci.d++; return ci; }
);

Example 6: Update nested array

const iassign = require("immutable-assign");

const o1 = { a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]], c2: {} }, b2: {} }, a2: {} };

// 6: Calling iassign() to push new item to o1.a.b.c[1]
const o2 = iassign(
    o1,
    function (o) { return o.a.b.c[1]; },
    function (c) { c.push(101); return c; }
);

Example 7: Update nested object, referring to external context.

const iassign = require("immutable-assign");

const o1 = { a: { b: { c: [{ d: 11, e: 12 }, { d: 21, e: 22 }] } } };

// 7: Calling iassign() to push increment to o1.a.b.c[0].d
const external = { a: 0 };

const o2 = iassign(
    o1,
    function (o, ctx) { return o.a.b.c[ctx.external.a]; },
    function (ci) { ci.d++; return ci; },
    { external: external }
);

Example 8: Update nested object using iassign.fp() and currying

const iassign = require("immutable-assign");

const nested1 = { a: { b: { c: [3, 4, 5] } } };


// 8.1: Calling iassign() to assign d to nested1.a.b 
const iassignFp = iassign.fp(undefined)
    (function (n) { return n.a.b; })
    (function (b) { b.d = 6; return b; })
    (undefined);

const nested2 = iassignFp(nested1);

// nested2 = { a: { b: { c: [3, 4, 5], d: 6 } } };
// nested2 !== nested1

// 8.2: Calling iassign() to increment nested2.a.b.d 
iassignFp = iassign.fp(undefined)
    (function (n) { return n.a.b.d; })
    (function (d) { return d + 1; })
    (undefined);
const nested3 = iassignFp(nested2);

// nested3 = { a: { b: { c: [3, 4, 5], d: 7 } } };
// nested3 !== nested2

// 8.3: Calling iassign() to push item to nested3.a.b.c 
iassignFp = iassign.fp(undefined)
    (function (n) { return n.a.b.c; })
    (function (c) { c.push(6); return c; })
    (undefined);
const nested4 = iassignFp(nested3);

// nested4 = { a: { b: { c: [3, 4, 5, 6], d: 7 } } };
// nested4 !== nested3

// 8.4: Calling iassign() to push item to nested3.a.b.c[1]
iassignFp = iassign.fp(undefined)
    (function (n, ctx) { return n.a.b.c[ctx.i]; })
    (function (ci) { return ci + 100; })
    ({i: 1});
const nested5 = iassignFp(nested4);

// nested5 = { a: { b: { c: [3, 104, 5, 6], d: 7 } } };
// nested5 !== nested4

Example 9: Support the ES6 Map

const iassign = require("immutable-assign");

const map1 = new Map();
map1.set("a", "value a");

iassign.setOption({
    copyFunc: function (value, propName) {
        if (value instanceof Map) {
            // In IE11, Map constructor arguments are not supported,
            // you need to provide ES6 shim, e.g., use core-js
            return new Map(value);
        }
    }
});

const map2 = iassign(
    map1,
    m => { m.set(1, 'first'); return m; }
);

// map2 !== map1
// map1 = Map({ "a": "value a" })
// map1 = Map({ "a": "value a", 1: "first" })

Example 10: Update nested level object properties using property paths (overload 3)

const iassign = require("immutable-assign");

const o1 = { a: { b: { c: [[{ d: 11, e: 12 }], [{ d: 21, e: 22 }]], c2: {} } } };

const o2 = iassign(
    o1,
    ['a', 'b', 'c', 0, '0'],
    (ci) => {
        ci.d++;
        return ci;
    });

// o2 = { a: { b: { c: [[{ d: 12, e: 12 }], [{ d: 21, e: 22 }]], c2: {} } } };
// o2 !== o1

Function Signature (TypeScript syntax)


// Return a new POJO object with property updated.

// function overload 1: you can skip getProp() if you trying to update the 1st level object properties, refer to example 1 and 2
iassign = function<TObj>(
    obj: TObj,                                          // POJO object to be getting the property from, it will not be modified.
    setProp: setPropFunc<TObj>,                         // Function to set the property.
    option?: IIassignOption): TObj;                     // (Optional) Options

// function overload 2: use getProp() to get prop paths
iassign = function<TObj, TProp, TContext>(
    obj: TObj,                                          // POJO object to be getting the property from, it will not be modified.
    getProp: (obj: TObj, context: TContext) => TProp,   // Function to get the property that needs to be updated.
    setProp: (prop: TProp) => TProp,                    // Function to set the property.
    context?: TContext,                                 // (Optional) Context to be used in getProp().
    option?: IIassignOption): TObj;                     // (Optional) Options

// function overload 3: pass in known property paths (array)
iassign = function<TObj, TProp, TContext>(
    obj: TObj,                                          // POJO object to be getting the property from, it will not be modified.
    propPaths: (string | number)[],                     // paths to the property that needs to be updated.
    setProp: setPropFunc<TObj>,                         // Function to set the property.
    option?: IIassignOption): TObj;                     // (Optional) Options

// functional programming friendly style, moved obj to the last parameter and supports currying, refer to example 8
iassign.fp = function <TObj, TProp, TContext>(
    option: IIassignOption,
    getPropOrPropPaths: getPropFunc<TObj, TProp, TContext> | (string | number)[],
    setProp: setPropFunc<TProp>,
    context?: TContext,
    obj?: TObj): TObj;                                  // POJO object to be getting the property from, it will not be modified.

// In ES6, you cannot set any property on imported modules directly, because they default
// to readonly, in this case, you need to use this method.
iassign.setOption(option: IIassignOption): void;

// Options: can be applied globally or individually
interface IIassignOption {
    freeze?: boolean;              // Deep freeze both input and output
    freezeInput?: boolean;         // Deep freeze input
    freezeOutput?: boolean;        // Deep freeze output
    useConstructor?: boolean;      // Uses the constructor to create new instances

    // Custom copy function, can be used to handle special types, e.g., Map, Set; refer to example 9
    copyFunc?: <T>(value: T, propName: string): T;

    // Disable validation for extra statements in the getProp() function, 
    // which is needed when running the coverage, e.g., istanbul.js does add 
    // instrument statements in our getProp() function, which can be safely ignored. 
    disableExtraStatementCheck?: boolean;

    // Return the same object if setProp() returns the input with no change.
    ignoreIfNoChange?: boolean;
}

Constraints

  • getProp() must be a pure function; I.e., it cannot access anything other than the input parameters. e.g., it must not access "this" or "window" objects. In addition, it must not modify the input parameters. It should only return a property that needs to be updated.

History

  • 2.1.5 - Migrated to github actions

  • 2.1.4 - Reduce the package size to 11.9 kB

  • 2.1.1 - Added function overload 3 to pass in known property paths (array). Refer to example 10

  • 2.0.8 - Fixed bug for undefined properties.

  • 2.0.4 - Replaced the proxy-polyfill with Object.defineProperty(), which has a much better browser support.

  • 2.0.1 - Minor bug fixes.

  • 2.0.0 -

  • 1.0.36 - Supports ES6 Map and Set. Refer to example 9

  • 1.0.35 - Supports ES6 default export.

  • 1.0.31 -

    • Added ignoreIfNoChange option, which causes iassign to return the same object if setProp() returns its parameter (i.e., reference pointer not changed).
    • Added setOption() function to allow you to set the iassign options globally in ES6.
  • 1.0.30 - Support classes

  • 1.0.29 - Supported ES6 Arrow Functions

  • 1.0.27 - Added iassign.fp() that support currying, refer to example 8

  • 1.0.26 - Works with webpack, please refer to ImmutableAssignTest

  • 1.0.23 - Greatly improved performance.

  • 1.0.21 - Added function overload 1 to skip getProp() if you trying to update the 1st level object properties, refer to example 1 and example 2

  • 1.0.20 - Added Travis-CI, Coveralls (coverage) and SauceLabs (browsers' tests)

  • 1.0.19 - Added TypeScript types to package.json

  • 1.0.18 - Tested on Mac (Safari 10 and Chrome 54)

  • 1.0.16 - Tested in Node.js and major browsers (IE 11, Chrome 52, Firefox 47, Edge 13, PhantomJS 2)