简体   繁体   中英

Is there a `.map` like function for objects? To create a new object with the same keys

With an array we can use the .map method to create a new map with the same array structure (ie. the same number of elements).

eg.

const array = [2, 4, 6]; 
const newArray = array.map(v => v *2); //[4, 8, 12]; 

I believe in functional programming this makes the array object what is called a functor .

With objects I want to do something similar - I want to create a new object with the same structure (same keys), as well as the same functions etc as the original object.

eg.

const obj = {
    foo: 2, 
    bar: 4, 
    biz: 6
}; 

const newObj = obj.map(v => v *2); // {foo: 4, bar: 8, biz: 12}

The way I'm currently doing this is by using Object.entries and .reduce :

 const obj = { foo: 2, bar: 4, biz: 6 }; const newObj = Object.entries(obj).reduce((acc,cur) => { return {...acc, [cur[0]]: cur[1] * 2 } }, {}); console.log(newObj);

I'm wondering - is there currently an object method that would let me do this that I'm missing? Or a proposal to add one?

There is no native map for objects, but can easily make one. I think entries is the simplest.

 const obj = { foo: 2, bar: 4, biz: 6 } const objMap = (obj, fn) => Object.fromEntries( Object.entries(obj).map( ([k, v], i) => [k, fn(v, k, i)] ) ) console.log( objMap(obj, v => v * 2), objMap(obj, (v, k) => `${k}-${v}`), objMap(obj, (v, _, i) => v * i) )

The map function for objects is easy enough to define.

 Object.defineProperty(Object.prototype, "map", { value(mapping) { const oldEntries = Object.entries(this); const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]); return Object.fromEntries(newEntries); } }); const obj = { foo: 2, bar: 4, baz: 6 }; const result = obj.map(x => 2 * x); console.log(result);

Note that this is different from kornieff's objMap function because the mapping function can't access or change the key. Hence, it's the correct implementation of the Functor type class for objects.


As a bonus, let's implement some other useful type classes for objects. First, the Representable type class.

 Object.tabulate = table => new Proxy({}, { get: (_, key) => table(key) }); const object = Object.tabulate(key => { if (key === "foo") return 10; if (key === "bar") return 20; return 30; }); console.log(object.foo); // 10 console.log(object.bar); // 20 console.log(object.baz); // 30

The tabulate function can be used to define various useful type classes such as Monad and Comonad . For example, let's implement the Distributive type class. I'll leave the implementation of the other type classes as an exercise for the reader.

 Object.tabulate = table => new Proxy({}, { get: (_, key) => table(key) }); const distribute = functor => Object.tabulate(key => functor.map(object => object[key])); const result1 = distribute([ { foo: 10, bar: 20 }, { foo: 30, bar: 40 } ]); console.log(result1.foo); // [ 10, 30 ] console.log(result1.bar); // [ 20, 40 ] Object.defineProperty(Object.prototype, "map", { value(mapping) { const oldEntries = Object.entries(this); const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]); return Object.fromEntries(newEntries); } }); const result2 = distribute({ a: { foo: 10, bar: 20 }, b: { foo: 30, bar: 40 } }); console.log(result2.foo); // { a: 10, b: 30 } console.log(result2.bar); // { a: 20, b: 40 }

Note that distribute expects an object that has a map method, which is why we defined the map method on Object.prototype . However, we could have used the Yoneda lemma to get around this restriction.

One way to do it:

 const obj = { foo: 2, bar: 4, biz: 6 }; const x = Object.keys(obj).map(o => { return {[o]: obj[o] * 2} }) console.log(JSON.stringify(x, null, 2))

Another way to do it using .reduce() is like this, making use of destructuring to read a little bit easier ( I think )

 const obj = { foo: 2, bar: 4, biz: 6 }; const newObj = Object.entries(obj).reduce( (acc, [key, value]) => ({...acc, [key]: value * 2}), {} ); console.log(newObj)

If you fancy experimenting a bit of functional apis in your code, then you could consider to use a functional library (such as Ramda),

It doesn't only introduce the functor api for objects (map), but also other cool stuff!

 const input = { a: 1, b: 44, c: 77 }; const doubled = R.map(R.multiply(2), input); console.log( `doubling input`, doubled, ); // and a lot more... const odds = R.filter(R.modulo(R.__, 2), input); console.log( `filtering odds only of input`, odds, );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>

you can use the function mapValues of lodash

const array = [2, 4, 6];

_.mapValues(array, v => v * 2)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM