Giter VIP home page Giter VIP logo

dot-prop-immutable's Introduction

dot-prop-immutable

npm version Build Status CodeQL

Immutable version of dot-prop with some extensions.

npm install dot-prop-immutable

The motivation for this module is to have a simple utility for changing state in a React-Redux application without mutating the existing state of plain JavaScript objects. If you are going for real immutable data collections take a look at the cool library Immutable.js. A good practice is not to mix the immutable data collections with mutable objects because it can lead to confusion. Immutable objects are not accessed by the default semantics, but implemented by setters and getters.

This library implements 3 helper functions:

get(object, path) --> value
set(object, path, value) --> object
delete(object, path) --> object

None of the functions mutate the input object. For efficiency, the returned object is not a deep clone of the original, but a shallow copy of the objects in the mutated path.

Usage

const dotProp = require('dot-prop-immutable');
let state = { todos: [] }, index = 0;

// Add todo:
state = dotProp.set(state, 'todos', list => [...list, {text: 'cleanup', complete: false}])
// or with destructuring assignment
state = {...state, todos: [...state.todos, {text: 'cleanup', complete: false}]};
//=>  { todos: [{text: 'cleanup', complete: false}] }

// Complete todo:
state = dotProp.set(state, `todos.${index}.complete`, true)
// or with destructuring assignment
state = {...state, todos: [
	...state.todos.slice(0, index),
	{...state.todos[index], complete: true},
	...state.todos.slice(index + 1)
]};
//=>  { todos: [{text: 'cleanup', complete: true}] }

// Delete todo:
state = dotProp.delete(state, `todos.${index}`)
// or with destructuring assignment
state = {...state, todos: [
	...state.todos.slice(0, index),
	...state.todos.slice(index + 1)
]};
//=>  { todos: [] }

get

Access a nested property by a dot path

// Getter
dotProp.get({foo: {bar: 'unicorn'}}, 'foo.bar')
//=> 'unicorn'

dotProp.get({foo: {bar: 'a'}}, 'foo.notDefined.deep')
//=> undefined

dotProp.get({foo: {bar: 'a'}}, 'foo.notDefined.deep', 'default value')
//=> default value

dotProp.get({foo: {'dot.dot': 'unicorn'}}, 'foo.dot\\.dot')
//=> 'unicorn'

or use a property array as a path.

// Use an array as get path
dotProp.get({foo: {'dot.dot': 'unicorn'}}, ['foo', 'dot.dot'])
//=> 'unicorn'

It is also possible to index into an array where the special index $end refers to the last element of the array.

const obj = {foo: [{ bar: 'gold-unicorn'}, 'white-unicorn', 'silver-unicorn']};

// Index into array
dotProp.get(obj, 'foo.1')
//=> 'white-unicorn'

dotProp.get(obj, 'foo.0.bar')
//=> 'gold-unicorn'

// Index into array with $end
dotProp.get(obj, 'foo.$end')
//=> 'silver-unicorn'

// If obj is an array
dotProp.get([{ bar: 'gold-unicorn'}, 'white-unicorn', 'silver-unicorn'], '0.bar')
//=> 'gold-unicorn'

set

Modify a nested property by a dot path

// Setter
const obj = {foo: {bar: 'a'}};

const obj1 = dotProp.set(obj, 'foo.bar', 'b');
//obj1 => {foo: {bar: 'b'}}

const obj2 = dotProp.set(obj1 , 'foo.baz', 'x');
//obj2 => {foo: {bar: 'b', baz: 'x'}}

where obj, obj1, obj2, obj3 all are different objects.

Use a function to modify the selected property, where first argument is the old value.

// Setter where value is a function (get and set current value)
dotProp.set({foo: {bar: 'a'}}, 'foo.bar', v => v + 'bc')
//=> {foo: {bar: 'abc'}}

Modify a nested array

const obj = {foo: [{ bar: 'gold-unicorn'}, 'white-unicorn', 'silver-unicorn']};

// Index into array
dotProp.set(obj, 'foo.1', 'platin-unicorn')
//=> {foo: [{bar: 'gold-unicorn'}, 'platin-unicorn', 'silver-unicorn']}

dotProp.set(obj, 'foo.0.bar', 'platin-unicorn')
//=> {foo: [{bar: 'platin-unicorn'}, 'white-unicorn', 'silver-unicorn']}

// Index into array with $end
dotProp.set(obj, 'foo.$end', 'platin-unicorn')
//=> {foo: [{ bar: 'gold-unicorn'}, 'white-unicorn', 'platin-unicorn']}

delete

Delete a nested property/array by a dot path

const obj = {foo: [{ bar: 'gold-unicorn'}, 'white-unicorn', 'silver-unicorn']};

// delete
dotProp.delete(obj, 'foo.$end');
//=> {foo: [{ bar: 'gold-unicorn'}, 'white-unicorn']}

dotProp.delete(obj, 'foo.0.bar');
//=> {foo: [{}, 'white-unicorn', 'silver-unicorn']}

toggle

Toggle a boolean a value by a dot path.

const obj = {foo: { bar: true } };

// toggle
dotProp.toggle(obj, 'foo.bar');
//=> {foo: { bar: false } }

merge

Merge a value by a dot path.

The target value must be an object, array, null, or undefined.

  • If target is an object, Object.assign({}, target, param) is used.
  • If target an array, target.concat(param) is used.
  • If target is null or undefined, the value is simply set.
const obj = {foo: { bar: {a:1, b:2 } };

// merge object
dotProp.merge(obj, 'foo.bar', {c:3} );
//=> {foo: { bar:{ a:1, b:2, c:3} } }

var arr = {foo: { bar: [1, 2] } };

// merge array
dotProp.merge(arr, 'foo.bar', [3, 4] );
//=> {foo: { bar:[1, 2, 3, 4 ] }

License

MIT

dot-prop-immutable's People

Contributors

cluk3 avatar coryhouse avatar dependabot[bot] avatar eagleeye avatar flamenco avatar hilleer avatar jonatanpedersen avatar kollner avatar nlepage avatar oligrand avatar praneshr avatar selfcontained avatar zaaack avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dot-prop-immutable's Issues

Add resource array path

Add resource array path ex: 'devices[0]': 'mobile', in get and set functions.
I can work on this if you want.

const person = dot.set(person, 'devices[0]', 'smartphone')
console.log(obj);
{
  "name":"xxxx",
   "devices":[
      "smartphone"
    ]
}

Polyfill Object.assign if necessary

Unfortunately, Internet Explorer 11 doesn't support Object.assign method. As I can see, it might be polyfilled with code from the MDN.

I'd like to suggest use polyfill code when Object.assign is not available and native one in other case. I'll prepare corresponding pull request if the change will be considered acceptable.

create array if dot path is integer and array doesn't exist in object

so, I've been using dot-prop-immutable in my web application and i love it but it doesn't create an array in the set method if the dot path has an array index and the array doesn't already exist in the object that's being modified. I have cloned the repository and added the functionality already so i'd love to make a pull request and add the feature if it is desired. thanks.

Making a path with period

const testConst = {
    [".github"]: "HelloWorld"
}

Currently if I had to edit something like the above constant testConst, I would write the path as ..github. This I assume proposes a problem as the package simply works on dot seperation. Is there any work around to this problem? If not then can this feature be added?

still active?

I noticed that it's been 11 months since anything was done with this project. I ran into the issue with default values, found out a fix was merged 11 months ago, but there hasn't been a release. Is this project still active?

Multiple '.' in attribute (e.g. dot.dot.dot) breaks

Indexing an object keyed by a hostname with subdomains (www.example.com) does not work, while a root level domain (example.com) does work.

dotProp.get({foo: {'dot.dot': 'unicorn'}}, 'foo.dot\\.dot') //=> 'unicorn'
dotProp.get({foo: {'dot.dot.dot': 'unicorn'}}, 'foo.dot\\.dot\\.dot') //=> undefined

The propToArray function is discarding its previous work after iteratively merging escaped path members:

ret.pop();
ret.push(last.slice(0, -1) + '.' + el);

Retaining the previous value and using it in the merge fixes the issue:

const prev = ret.pop();
ret.push(prev.slice(0, -1) + '.' + el);

Didn't seem worth the effort to fork the repo, make the fix, and submit a PR for a two line fix, so I'm submitting an issue with the path instead.

Add increment() and decrement()

This is a feature request. There is toggle() for a boolean value and I would like to have similar stuff for numbers.

Example: the following code

const deadCount = state.players[playerIndex].commander.deadCount + 1;
state = dotProp.merge(state, `players.${playerIndex}.commander`, { deadCount });

could be replaced with

state = dotProp.increment(state, `players.${playerIndex}.commander.deadCount`);

I used merge instead of set because the property deadCount might be not initialized. In this case, we can treat it as 0.

dotpro.delete() cause an error with Angular9/TS3.7

Hello, i was using your lib with Angular 8 and Typescript 3.5, but i've upgraded my app to Angular 9/Typescript 3.7 and now i have an error with dotprop.delete().

dotdrop-angular9

Look like it won't let me assign a Partial<> on my state anymore. ๐Ÿค”

Add functions to update items in array

Feature request

I would like to see set of methods for bulk work with arrays items. They might be built on top of existing ones (e.g. set, merge, toggle, etc).

Example of verbose code:

const units = state.units.map(unit => {
  if (unit.team === team) {
    return ({ ...unit, selected: true });
  } else {
    return unit;
  }
});
state = { ...state, units };

we can invent something like:

state = dotProps.mergeForArrayItems(state, 'units', { selected: true }, unit => unit.team === team);

or using dedicated namespace

state = dotProps.array.merge(state, 'units', { selected: true }, unit => unit.team === team);
// signature: merge(obj, prop, val [, predicate])

Exception thrown when adding an item to array with $end, and the array is empty

Example:
let obj={a:[]};
dotProp.set(Obj, 'a.$end', 5);
index.js:147 Uncaught (in promise) Error: Array index '-1' has to be an integer
The behavior when the array is empty should be consistent with the case when the array is not empty.

function getArrayIndex(head, obj) { if (head === '$end') { head = obj.length - 1; } if (!/^\+?\d+$/.test(head)) { throw new Error('Array index \'' + head + '\' has to be an integer'); } return parseInt(head); }

Multiple assignments

It would be nice if it supported multiple assignments.
For ex:
dotProp.mset (state, {path1 : value1, path2 : value2})

Unexpected behavior when an intermediate key is `null`

When attempting to access a a nested attribute of a null value, a TypeError is encountered.

The below test suite illustrates the behavior.

Is this intended, or something that can/should be handled in the library?

describe('dot-prop-immutable get behavior', () => {
  const testObject = {
    a: {
      b: {
        c: 1
      },
      nullKey: null
    }
  };
  it('returns correct value when all keys present', () => {
    expect(dotProp.get(testObject, 'a.b.c')).toBe(1);
  });

  it('returns undefined when an intermediate key is missing', () => {
    expect(dotProp.get(testObject, 'a.missingKey.c')).toBeUndefined();
  });

  it('returns null when a leaf key is null', () => {
    expect(dotProp.get(testObject, 'a.nullKey')).toBeNull();
  });

  it('returns undefined when an intermediate key is missing', () => {
    expect(dotProp.get(testObject, 'a.missingKey.c')).toBeUndefined();
  });

  // This test currently fails.  My expectation is that it would return
  // undefined.
  it('returns undefined when an intermediate key is null', () => {
    expect(dotProp.get(testObject, 'a.nullKey.anotherKey')).toBeUndefined();
  });
});
 FAIL  src/__test__/utils.test.js
  โ— dot-prop-immutable get behavior โ€บ returns undefined when an intermediate key is null

    TypeError: Cannot read property 'anotherKey' of null

      at Object.get (node_modules/dot-prop-immutable/index.js:48:13)
      at Object.it (src/__test__/utils.test.js:206:39)
          at Promise (<anonymous>)
      at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
          at <anonymous>
      at process._tickCallback (internal/process/next_tick.js:188:7)
  dot-prop-immutable get behavior
    โœ“ returns correct value when all keys present
    โœ“ returns undefined when an intermediate key is missing (1ms)
    โœ“ returns null when a leaf key is null
    โœ“ returns undefined when an intermediate key is missing
    โœ• returns undefined when an intermediate key is null

Test Suites: 1 failed, 1 total

Discussion: Mutation โ€“ Shallow / Deep copy

Hey @debitoor, hope all is good!
This is not really an issue, more a question I'd say.


When I read the intro;

The motivation for this module is to have a simple utility for changing state in a React-Redux application without mutating the existing state of plain JavaScript objects.

..then I thought, ah, perfect. I can use this to update nested objects "without mutating the existing state" in my "React/Redux"-app. But I continued reading:

None of the functions mutate the input object. For efficiency, the returned object is not a deep clone of the original, but a shallow copy of the objects in the mutated path.

Not returning a deep clone of the original object is contradicting the earlier paragraph โ€“ since changing a shallow copy will mutate the existing object โ€“ and hence the state.

What are your thoughts on this? Did I miss something perhaps?

TypeError: Cannot read property 'XXXXXX' of null at Object.get

Say you have a data object of an issue from Github, where content: null:

{ column: { name: 'To do' }, isArchived: false, content: null }

and you try to get a sub property of content:

dotProp.get(issue, 'content.labels');

it throws an error:

TypeError: Cannot read property 'labels' of null at Object.get

[Proposal] Strongly typed Pick / Omit

Actually, it is as simple as

import dotProp from 'dot-prop-immutable'

export function pick<T> (el: T, select: (string | number)[]): Partial<T> {
  let p = {} as Partial<T>

  select.map((k) => {
    p = dotProp.set(p, k, dotProp.get(el, k))
  })

  return p
}

export function omit<T> (el: T, deSelect: (string | number)[]): Partial<T> {
  let p = el as Partial<T>

  deSelect.map((k) => {
    p = dotProp.delete(el, k)
  })

  return p
}

However, I cannot make it strongly typed for select / deSelect that contains no dot...

Use number as a path

dotProp.set({}, 0, 'a'); // Returns 'a' instead of {'0': 'a'}

This causes the result indifferent to my intention.
So I use toString() when path is a Number now.

I think it's very useful and consistent if I can use a number path.

Map keys with dots are interpreted as paths.

I am trying to not use a property array for addressing a map with string/dot keys.
I am writing a patch to address this. Please respond with your thoughts....

const state = {
 "map" : { 
 "http://foo.bar" : 1,
 "http://me.you" : 2
 }
}

I can't address the keys with dots.

Solution #1 Use Brackets

dotProp.set(state, "map['http://foo.bar']", 3)

Solution #2A Escape Dots With Backslash

dotProp.set(state, "map.http://foo\\.bar", 3)

Solution #2B Escape Dots With Dot

dotProp.set(state, "map.http://foo..bar", 3)

Solution #3 Use quotes/apostrophes

dotProp.set(state, 'map."http://foo.bar"', 3)

Don't default missing key on set

currently, if I have

const state = {}

and I attempt to update the state with devices like so,

const id = '123'
dotProp.set(state, 'devices', list => [...list, id])

I will get an error because list is being defaulted to an object. Instead it would be nice to leave that as undefined for the user to be able to use default params to have better control. So instead it would look like this,

const id = '123'
dotProp.set(state, 'devices', (list = []) => [...list, id])

or if I want my devices to be an object keyed on the ids I could do something like,

const device = {id:'123'}
dotProp.set(state, 'devices', (devices = {}) => {...devices, device})

An alternative approach would be to have a optional parameter at the end to change the default value from {} to []

could not delete undefined element in an array

I want to delete an element in an array with index, but the element is undefined. After I call the delete method, it return the array which is not be deleted the undefined

const b = [1, 2, undefined, 3, 4]
dotProp.delete(b, 2) //will return [1, 2, undefined, 3, 4], rather than [1, 2, 3, 4]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.