map function for objects (instead of arrays)

前端 未结 30 1917
无人及你
无人及你 2020-11-22 04:23

I have an object:

myObject = { \'a\': 1, \'b\': 2, \'c\': 3 }

I am looking for a native method, similar to Array.prototype.map

相关标签:
30条回答
  • 2020-11-22 04:43

    To responds more closely to what precisely the OP asked for, the OP wants an object:

    myObject = { 'a': 1, 'b': 2, 'c': 3 }

    to have a map method myObject.map,

    similar to Array.prototype.map that would be used as follows:

    newObject = myObject.map(function (value, label) {
        return value * value;
    });
    // newObject is now { 'a': 1, 'b': 4, 'c': 9 }

    The imho best (measured in terms to "close to what is asked" + "no ES{5,6,7} required needlessly") answer would be:

    myObject.map = function mapForObject(callback)
    {
      var result = {};
      for(var property in this){
        if(this.hasOwnProperty(property) && property != "map"){
          result[property] = callback(this[property],property,this);
        }
      }
      return result;
    }
    

    The code above avoids intentionally using any language features, only available in recent ECMAScript editions. With the code above the problem can be solved lke this:

    myObject = { 'a': 1, 'b': 2, 'c': 3 };
    
    myObject.map = function mapForObject(callback)
    {
      var result = {};
      for(var property in this){
        if(this.hasOwnProperty(property) && property != "map"){
          result[property] = callback(this[property],property,this);
        }
      }
      return result;
    }
    
    newObject = myObject.map(function (value, label) {
      return value * value;
    });
    console.log("newObject is now",newObject);
    alternative test code here

    Besides frowned upon by some, it would be a possibility to insert the solution in the prototype chain like this.

    Object.prototype.map = function(callback)
    {
      var result = {};
      for(var property in this){
        if(this.hasOwnProperty(property)){
          result[property] = callback(this[property],property,this);
        }
      }
      return result;
    }
    

    Something, which when done with careful oversight should not have any ill effects and not impact map method of other objects (i.e. Array's map).

    0 讨论(0)
  • 2020-11-22 04:44

    This is really annoying, and everyone in the JS community knows it. There should be this functionality:

    const obj1 = {a:4, b:7};
    const obj2 = Object.map(obj1, (k,v) => v + 5);
    
    console.log(obj1); // {a:4, b:7}
    console.log(obj2); // {a:9, b:12}
    

    here is the naïve implementation:

    Object.map = function(obj, fn, ctx){
    
        const ret = {};
    
        for(let k of Object.keys(obj)){
            ret[k] = fn.call(ctx || null, k, obj[k]);
        });
    
        return ret;
    };
    

    it is super annoying to have to implement this yourself all the time ;)

    If you want something a little more sophisticated, that doesn't interfere with the Object class, try this:

    let map = function (obj, fn, ctx) {
      return Object.keys(obj).reduce((a, b) => {
        a[b] = fn.call(ctx || null, b, obj[b]);
        return a;
      }, {});
    };
    
    
    const x = map({a: 2, b: 4}, (k,v) => {
        return v*2;
    });
    

    but it is safe to add this map function to Object, just don't add to Object.prototype.

    Object.map = ... // fairly safe
    Object.prototype.map ... // not ok
    
    0 讨论(0)
  • 2020-11-22 04:44

    Object Mapper in TypeScript

    I like the examples that use Object.fromEntries such as this one, but still, they are not very easy to use. The answers that use Object.keys and then look up the key are actually doing multiple look-ups that may not be necessary.

    I wished there was an Object.map function, but we can create our own and call it objectMap with the ability to modify both key and value:

    Usage (JavaScript):

    const myObject = { 'a': 1, 'b': 2, 'c': 3 };
    
    // keep the key and modify the value
    let obj = objectMap(myObject, val => val * 2);
    // obj = { a: 2, b: 4, c: 6 }
    
    
    // modify both key and value
    obj = objectMap(myObject,
        val => val * 2 + '',
        key => (key + key).toUpperCase());
    // obj = { AA: '2', BB: '4', CC: '6' }
    

    Code (TypeScript):

    interface Dictionary<T> {
        [key: string]: T;
    }
    
    function objectMap<TValue, TResult>(
        obj: Dictionary<TValue>,
        valSelector: (val: TValue, obj: Dictionary<TValue>) => TResult,
        keySelector?: (key: string, obj: Dictionary<TValue>) => string,
        ctx?: Dictionary<TValue>
    ) {
        const ret = {} as Dictionary<TResult>;
        for (const key of Object.keys(obj)) {
            const retKey = keySelector
                ? keySelector.call(ctx || null, key, obj)
                : key;
            const retVal = valSelector.call(ctx || null, obj[key], obj);
            ret[retKey] = retVal;
        }
        return ret;
    }
    

    If you are not using TypeScript then copy the above code in TypeScript Playground to get the JavaScript code.

    Also, the reason I put keySelector after valSelector in the parameter list, is because it is optional.

    * Some credit go to alexander-mills' answer.

    0 讨论(0)
  • 2020-11-22 04:45

    I needed a version that allowed modifying the keys as well (based on @Amberlamps and @yonatanmn answers);

    var facts = [ // can be an object or array - see jsfiddle below
        {uuid:"asdfasdf",color:"red"},
        {uuid:"sdfgsdfg",color:"green"},
        {uuid:"dfghdfgh",color:"blue"}
    ];
    
    var factObject = mapObject({}, facts, function(key, item) {
        return [item.uuid, {test:item.color, oldKey:key}];
    });
    
    function mapObject(empty, obj, mapFunc){
        return Object.keys(obj).reduce(function(newObj, key) {
            var kvPair = mapFunc(key, obj[key]);
            newObj[kvPair[0]] = kvPair[1];
            return newObj;
        }, empty);
    }
    

    factObject=

    {
    "asdfasdf": {"color":"red","oldKey":"0"},
    "sdfgsdfg": {"color":"green","oldKey":"1"},
    "dfghdfgh": {"color":"blue","oldKey":"2"}
    }
    

    Edit: slight change to pass in the starting object {}. Allows it to be [] (if the keys are integers)

    0 讨论(0)
  • 2020-11-22 04:48

    JavaScript just got the new Object.fromEntries method.

    Example

    function mapObject (obj, fn) {
      return Object.fromEntries(
        Object
          .entries(obj)
          .map(fn)
      )
    }
    
    const myObject = { a: 1, b: 2, c: 3 }
    const myNewObject = mapObject(myObject, ([key, value]) => ([key, value * value]))
    console.log(myNewObject)

    Explanation

    The code above converts the Object into an nested Array ([[<key>,<value>], ...]) wich you can map over. Object.fromEntries converts the Array back to an Object.

    The cool thing about this pattern, is that you can now easily take object keys into account while mapping.

    Documentation

    • Object.fromEntries()
    • Object.entries()

    Browser Support

    Object.fromEntries is currently only supported by these browsers/engines, nevertheless there are polyfills available (e.g @babel/polyfill).

    0 讨论(0)
  • 2020-11-22 04:48

    The accepted answer has two drawbacks:

    • It misuses Array.prototype.reduce, because reducing means to change the structure of a composite type, which doesn't happen in this case.
    • It is not particularly reusable

    An ES6/ES2015 functional approach

    Please note that all functions are defined in curried form.

    // small, reusable auxiliary functions
    
    const keys = o => Object.keys(o);
    
    const assign = (...o) => Object.assign({}, ...o);
    
    const map = f => xs => xs.map(x => f(x));
    
    const mul = y => x => x * y;
    
    const sqr = x => mul(x) (x);
    
    
    // the actual map function
    
    const omap = f => o => {
      o = assign(o); // A
      map(x => o[x] = f(o[x])) (keys(o)); // B
      return o;
    };
    
    
    // mock data
    
    const o = {"a":1, "b":2, "c":3};
    
    
    // and run
    
    console.log(omap(sqr) (o));
    console.log(omap(mul(10)) (o));

    • In line A o is reassigned. Since Javascript passes reference values by sharing, a shallow copy of o is generated. We are now able to mutate o within omap without mutating o in the parent scope.
    • In line B map's return value is ignored, because map performs a mutation of o. Since this side effect remains within omap and isn't visible in the parent scope, it is totally acceptable.

    This is not the fastest solution, but a declarative and reusable one. Here is the same implementation as a one-line, succinct but less readable:

    const omap = f => o => (o = assign(o), map(x => o[x] = f(o[x])) (keys(o)), o);
    

    Addendum - why are objects not iterable by default?

    ES2015 specified the iterator and iterable protocols. But objects are still not iterable and thus not mappable. The reason is the mixing of data and program level.

    0 讨论(0)
提交回复
热议问题