GithubHelp home page GithubHelp logo

dset's Introduction

dset CI codecov

A tiny (194B) utility for safely writing deep Object values~!

For accessing deep object properties, please see dlv.

Using GraphQL? You may want dset/merge – see Merging for more info.

Install

$ npm install --save dset

Modes

There are two "versions" of dset available:

dset

Size (gzip): 194 bytes
Availability: CommonJS, ES Module, UMD

import { dset } from 'dset';

dset/merge

Size (gzip): 288 bytes
Availability: CommonJS, ES Module, UMD

import { dset } from 'dset/merge';

Usage

import { dset } from 'dset';

let foo = { abc: 123 };
dset(foo, 'foo.bar', 'hello');
// or: dset(foo, ['foo', 'bar'], 'hello');
console.log(foo);
//=> {
//=>   abc: 123,
//=>   foo: { bar: 'hello' },
//=> }

dset(foo, 'abc.hello', 'world');
// or: dset(foo, ['abc', 'hello'], 'world');
console.log(foo);
//=> {
//=>   abc: { hello: 'world' },
//=>   foo: { bar: 'hello' },
//=> }

let bar = { a: { x: 7 }, b:[1, 2, 3] };
dset(bar, 'b.1', 999);
// or: dset(bar, ['b', 1], 999);
// or: dset(bar, ['b', '1'], 999);
console.log(bar);
//=> {
//=>   a: { x: 7 },
//=>   b: [1, 999, 3],
//=> }

dset(bar, 'a.y.0', 8);
// or: dset(bar, ['a', 'y', 0], 8);
// or: dset(bar, ['a', 'y', '0'], 8);
console.log(bar);
//=> {
//=>   a: {
//=>     x: 7,
//=>     y: [8],
//=>   },
//=>   b: [1, 999, 3],
//=> }

let baz = {};
dset(baz, 'a.0.b.0', 1);
dset(baz, 'a.0.b.1', 2);
console.log(baz);
//=> {
//=>   a: [{ b: [1, 2] }]
//=> }

Merging

The main/default dset module forcibly writes values at the assigned key-path. However, in some cases, you may prefer to merge values at the key-path. For example, when using GraphQL's @stream and @defer directives, you will need to merge the response chunks into a single object/list. This is why dset/merge exists~!

Below is a quick illustration of the difference between dset and dset/merge:

let input = {
  hello: {
    abc: 123
  }
};

dset(input, 'hello', { world: 123 });
console.log(input);

// via `dset`
//=> {
//=>   hello: {
//=>     world: 123
//=>   }
//=> }

// via `dset/merge`
//=> {
//=>   hello: {
//=>     abc: 123,
//=>     world: 123
//=>   }
//=> }

Immutability

As shown in the examples above, all dset interactions mutate the source object.

If you need immutable writes, please visit clean-set (182B).
Alternatively, you may pair dset with klona, a 366B utility to clone your source(s). Here's an example pairing:

import { dset } from 'dset';
import { klona } from 'klona';

export function deepset(obj, path, val) {
  let copy = klona(obj);
  dset(copy, path, val);
  return copy;
}

API

dset(obj, path, val)

Returns: void

obj

Type: Object

The Object to traverse & mutate with a value.

path

Type: String or Array

The key path that should receive the value. May be in x.y.z or ['x', 'y', 'z'] formats.

Note: Please be aware that only the last key actually receives the value!

Important: New Objects are created at each segment if there is not an existing structure.
However, when integers are encounted, Arrays are created instead!

value

Type: Any

The value that you want to set. Can be of any type!

Benchmarks

For benchmarks and full results, check out the bench directory!

# Node 10.13.0

Validation:
  ✔ set-value
  ✔ lodash/set
  ✔ dset

Benchmark:
  set-value    x 1,701,821 ops/sec ±1.81% (93 runs sampled)
  lodash/set   x   975,530 ops/sec ±0.96% (91 runs sampled)
  dset         x 1,797,922 ops/sec ±0.32% (94 runs sampled)

Related

  • dlv - safely read from deep properties in 120 bytes
  • dequal - safely check for deep equality in 247 bytes
  • klona - quickly "deep clone" data in 200 to 330 bytes
  • clean-set - fast, immutable version of dset in 182 bytes

License

MIT © Luke Edwards

dset's People

Contributors

akkuma avatar bgoscinski avatar bwendt-mylo avatar emiltholin avatar fortizde avatar lukeed avatar maraisr avatar n1ru4l avatar rohit2sharma95 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

dset's Issues

Not really an issue.

@lukeed -Good work!
This, inevitably, reminded me of a similar thing I wrote 4 years ago. I believe now, looking in retrospect, that my error was to use latin names for the exposed methods.

Might warrant a re-write/review.

Consider adding array merge strategy option

Given following code:

const host = { arr: ['hello'] }
const { dset } = require('dset/merge')
dset(host, 'arr', ['world'])

// or
const host = { arr: ['hello'] }
const { merge } = require('dset/merge')
merge({arr: ['hello']}, {arr: ['world']})

I would expect the result to be { arr: ['hello', 'world'] }, however currently the result is { arr: ['world'] }
If I change the second arr to barr the result is { arr: ['hello'], barr: ['world'] }

The README says:

The main/default dset module forcibly writes values at the assigned key-path. However, in some cases, you may prefer to merge values at the key-path

So I would expect arrays to merge too instead of being forcibly overwritten as in the 'standard' dset
Anyhow i understand the merge logic is similar to lodash.merge.
The deepmerge module has a merge strategy option, but it only handles merging, not what dset does.
It would be great if dset could be used with a strategy of "add" instead of "overwrite".

+ strat = strat || (a, b, k) => { a[k] = merge(a[k], b[k]) };
if (Array.isArray(a) && Array.isArray(b)) {
  for (k=0; k < b.length; k++) {
-    a[k] = merge(a[k], b[k]);
+   strat(a, b, k)
  }
}
+ // alt strat: (a, b, k) => a.push(b[k])

Prototype pollution vulnerability in merge function

A vulnerability detection tool I use is flagging the merge function, and particularly this line: https://github.com/lukeed/dset/blob/master/src/merge.js#L9

The tool says that the function is vulnerable to prototype pollution as per this paper: https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf

Reading through the code and the paper, it seems like there is a check to mitigate prototype pollution in the dset function that uses merge, but not in merge itself. Would the merge function benefit from such a check? Or would that break something?

Thanks,
Simon

Vulnerability version 3.1.3

Identifiers
pkg:npm/[email protected] (Confidence:Highest)
cpe:2.3:a:dset_project:dset:3.1.3:::::::* (Confidence:Highest)
Published Vulnerabilities
CVE-2022-25645

All versions of package dset are vulnerable to Prototype Pollution via 'dset/merge' mode, as the dset function checks for prototype pollution by validating if the top-level path contains proto, constructor or protorype. By crafting a malicious object, it is possible to bypass this check and achieve prototype pollution.
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

CVSSv2:
Base Score: MEDIUM (6.8)
Vector: /AV:N/AC:M/Au:N/C:P/I:P/A:P
CVSSv3:
Base Score: HIGH (8.1)
Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

References:
MISC - https://github.com/lukeed/dset/blob/master/src/merge.js%23L9
MISC - #38
MISC - https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-2431974
MISC - https://snyk.io/vuln/SNYK-JS-DSET-2330881
Vulnerable Software & Versions:

cpe:2.3:a:dset_project:dset::::::node.js::*

Identifiers
pkg:npm/[email protected] (Confidence:Highest)
cpe:2.3:a:dset_project:dset:3.1.3:::::::* (Confidence:Highest)
Published Vulnerabilities
CVE-2022-25645

All versions of package dset are vulnerable to Prototype Pollution via 'dset/merge' mode, as the dset function checks for prototype pollution by validating if the top-level path contains proto, constructor or protorype. By crafting a malicious object, it is possible to bypass this check and achieve prototype pollution.
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

CVSSv2:
Base Score: MEDIUM (6.8)
Vector: /AV:N/AC:M/Au:N/C:P/I:P/A:P
CVSSv3:
Base Score: HIGH (8.1)
Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

References:
MISC - https://github.com/lukeed/dset/blob/master/src/merge.js%23L9
MISC - #38
MISC - https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-2431974
MISC - https://snyk.io/vuln/SNYK-JS-DSET-2330881
Vulnerable Software & Versions:

cpe:2.3:a:dset_project:dset::::::node.js::*

Sweet!

Very cool and useful little lib, thanks!!

`dset` creates array instead of object for nested empty string property

This test currently fails:

	objects(`should ${verb} empty string property`, () => {
		let { input } = prepare({});

		dset(input, ["hello", ""], 123);

		assert.equal(input, {
			hello: { "": 123 },
		});
	});

with

   FAIL  objects  "should merge empty string property"
    Expected values to be deeply equal:  (equal)

        ··{
        Actual:
        --··"hello":·[]
        Expected:
        ++··"hello":·{
        ++····"":·123
        ++··}
        ··}

Prototype pollution vulnerability

Hi,
Dset is reporting one critical vulnerability. Below is mentioned error.
"Prototype pollution vulnerability in dset allows attacker to cause a denial of service and may lead to remote code execution."

Would you please help here?

Thanks
Shailendra

Possibility to delete properties by path?

Is it possible to not only set values, but also delete properties at a given path?

const obj = {
  items: [
    { 
      firstName: "peter",
      lastName: "parker"
    }
  ]
}

What I would need in my case would be:

delete obj.items[0].lastName;

Obviously I can set the value to null or undefined using set:

dset(obj, "items[0].lastName", null)

but I actually need to be able to delete the property completely. Is this possible using dset?

dget?

Thinking about what would an appropriate dget look like, where dget is a function that takes the same type of dset string x.y.z? Any thoughts on this yet?

Use UMD

This may seem silly, but if you use UMD for your node version, it would also work for vanilla browser legacy script tags.

Just use Rollup with a umd target. Two for one, what a deal.

Returning the modified object would simplify immutability

dset mutates the source object but doesn't return it. If the object was returned, we would get these advantages:

  • would simplify the case of immutability: instead of having to create a wrapper (as suggested in this section in the README), we could simply do modifiedObj = dset(klona(originalObj), keyPath, val)
  • would make dset more aligned with it's cousin Object.assign, which "copies all enumerable own properties to a target object and returns the modified target object"

I'm happy to submit a PR if this is a desired feature.

Strange bug using dset with mobx

I noticed that there is a weird bug when using dset with mobx:

dset(data, "a.b.c", 1)

If data is an mobx observable (a deep and proxied one), dset will set only the first key a with an empty object.
The result will be:

{ a: {} }

instead of

{ a: { b: { c: 1 } } }

It happens only when using deep and proxy options in mobx (that are the default options).

Struggling with it, I found out that it is the multiple assignment used in dset function that breaks it.
It can be fixed splitting the multiple assignment into 2 different lines.

I created a sandbox that shows everything I state here (fix included):
https://codesandbox.io/s/dset-mobx-issue-x99gw

Immutable structures cannot be used unless a copy is created

The way the library works at its current state is that it modifies the original passed object, requiring a bit of boilerplate for copy creation before passing a value in to dset.

A breaking update could introduce the ability to create a copy, modify it, and return the result, all internally. It could also be a non-breaking update in which a flag is added as the fourth parameter to determine whether a copy should be created or if the passed value should be directly modified.

I'm willing to PR this.

Break loop on magical key?

Right now, as of 2.1.0, if a key named __proto__, constructor, or prototype is found, that assignment is skipped: https://github.com/lukeed/dset/blob/master/src/index.js#L6

But, in preparing for the 3.0 version, I'm left wondering if those keys should break the loop entirely?
The reasoning is that if those keys are showing up, it's (most likely) a malicious call in the first place, so we might as well throw away the entire operation.

Without breaking, something like this is still possible:

let user = { age: 123 };
dset(user, '__proto__.isAdmin', true);
//=> skip "__proto__" key
//=> assign "isAdmin" key to root object

console.log(user);
//=> { age: 123, isAdmin: true }

While still mutated, this is still considered safe because only the given user object is affect. New objects (or new User objects) are not polluted – aka, do not inherit – a new isAdmin = true base property.

The above is effectively the same thing as this:

let user = { age: 123 };

dset(user, 'isAdmin', true);
//=> assign "isAdmin" key to root object

console.log(user);
//=> { age: 123, isAdmin: true }

Breaking on "__proto__" key (and the others) would change the 1st snippet such that nothing changes on the user object. Its final value would be { age: 123 } instead of the current { age: 123, isAdmin: true }.

Should throw if falsey key value exists

In your example, if foo.d is already set to say 5, it throws. I would expect that. However, if it is 0 (or anything falsey), it paves over the falsey value.

{a: 1, b: 2, d: 0}
//=> { a:1, b:2, d:{ e:{ f:'hello' } } };

A possible fix is:

x = o[keys[i]] === undefined ? {} : o[keys[i]];

Accept numbers as path elements

This seems like a reasonable input for me. Both ramda & lodash accept it.

var obj = {}
dset(obj, ['foo', 10, 'baz'], 'bar')

Mentioned libraries also handle this by creating an object instead of array (like in my previously reported issue):

var obj = {}
dset(obj, ['foo', 10.2, 'baz'], 'bar')

`dset` throws when encountering `null` in object

This test currently fails:

  objects(`should ${verb} null values`, () => {
    let { input } = prepare({ hello: null });

    dset(input, ['hello', 'a'], 123);

    assert.equal(input, {
      hello: { a: 123 },
    });
  });

with:

   FAIL  objects  "should overwrite null values"
    Cannot set properties of null (setting 'a')

lodash.set correctly overwrites null.

Merging objects at the root results in a no-op

👋 I believe I have discovered a bug in this library based on GraphiQL usage of the library. If given:

import { dset } from "dset/merge"
let input = {};
dset(input, [], { hero: { id: "A" }});
console.log(input)
// output: {}
// expected: { hero: { id: "A" } }

In the case of GraphQL, this can happen if a field is deferred at the root level (example test case from the reference implementation).

Example commit with test case and a fix:
kirkbyo@ffc84ff

Happy to open that commit as a PR here if you would prefer.

Trying to get in touch regarding a security issue

Hey there!

I'd like to report a security issue but cannot find contact instructions on your repository.

If not a hassle, might you kindly add a SECURITY.md file with an email, or another contact method? GitHub recommends this best practice to ensure security issues are responsibly disclosed, and it would serve as a simple instruction for security researchers in the future.

Thank you for your consideration, and I look forward to hearing from you!

(cc @huntr-helper)

npm package not in registry

❯ yarn add deepset                                                                                                             2s 815ms
yarn add v1.3.2
warning ../../package.json: No license field
[1/4] 🔍  Resolving packages...
error Couldn't find package "deepset" on the "npm" registry.
info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command.

❯ npm search deepset                                                                                                            3s 37ms
NAME                      | DESCRIPTION          | AUTHOR          | DATE       | VERSION  | KEYWORDS
compute-erf               | Error function.      | =kgryte…        | 2015-08-01 | 3.0.3    | compute.io compute computation erf error f
compute-signum            | Signum function.     | =kgryte…        | 2015-06-20 | 2.0.0    | compute.io compute computation sign signum
compute-erfinv            | Inverse error…       | =kgryte…        | 2016-01-30 | 3.0.1    | compute.io compute computation statistics
compute-sqrt              | Computes an…         | =kgryte         | 2015-07-22 | 3.0.1    | compute.io compute computation mathematics
vue-deepset               | Deep set Vue.js…     | =vbranden       | 2018-01-28 | 0.6.3    | vue vue.js reactive deep set vuex model v-
compute-erfc              | Complementary error… | =kgryte…        | 2015-07-22 | 3.0.1    | compute.io compute computation statistics
compute-abs               | Computes an…         | =kgryte…        | 2015-07-16 | 3.0.0    | compute.io compute computation mathematics
compute-erfcinv           | Inverse…             | =kgryte…        | 2016-01-30 | 3.0.1    | compute.io compute computation erf error f

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.