fonction de carte pour les objets (au lieu des tableaux)

1068

J'ai un objet:

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

Je recherche une méthode native, similaire à Array.prototype.mapcelle qui serait utilisée comme suit:

newObject = myObject.map(function (value, label) {
    return value * value;
});

// newObject is now { 'a': 1, 'b': 4, 'c': 9 }

JavaScript a-t-il une telle mapfonction pour les objets? (Je veux cela pour Node.JS, donc je ne me soucie pas des problèmes inter-navigateurs.)

Randomblue
la source
1
La plupart des réponses utilisent Object.keys, qui n'a pas d'ordre bien défini. Cela peut être problématique, je suggère d'utiliser à la Object.getOwnPropertyNamesplace.
Oriol
14
Étonnant pour moi que JS n'ait pas prévu cette tâche incroyablement rudimentaire.
jchook
3
@Oriol en êtes-vous sûr? Selon les documents Web MDN, l'ordre des éléments du tableau est cohérent entre Object.keyset Object.getOwnPropertyNames. Voir developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Bart
@Bart L'ordre de Object.keysdépend de l'implémentation. Voir stackoverflow.com/a/30919039/1529630
Oriol
4
@Oriol De toute façon, vous n'êtes pas censé vous fier à l'ordre des clés dans les objets, donc cela n'a pas de rapport avec la question - car il n'a jamais précisé que l'ordre lui importait. Et si l'ordre lui importait, il ne devrait en aucun cas utiliser un objet.
BT

Réponses:

1545

Il n'y a pas de natif mapde l' Objectobjet, mais qu'en est-il:

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

Object.keys(myObject).map(function(key, index) {
  myObject[key] *= 2;
});

console.log(myObject);
// => { 'a': 2, 'b': 4, 'c': 6 }

Mais vous pouvez facilement parcourir un objet en utilisant for ... in:

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

for (var key in myObject) {
  if (myObject.hasOwnProperty(key)) {
    myObject[key] *= 2;
  }
}

console.log(myObject);
// { 'a': 2, 'b': 4, 'c': 6 }

Mise à jour

Beaucoup de gens mentionnent que les méthodes précédentes ne retournent pas un nouvel objet, mais opèrent plutôt sur l'objet lui-même. D'ailleurs, je voulais ajouter une autre solution qui renvoie un nouvel objet et laisse l'objet d'origine tel qu'il est:

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

// returns a new object with the values at each key mapped using mapFn(value)
function objectMap(object, mapFn) {
  return Object.keys(object).reduce(function(result, key) {
    result[key] = mapFn(object[key])
    return result
  }, {})
}

var newObject = objectMap(myObject, function(value) {
  return value * 2
})

console.log(newObject);
// => { 'a': 2, 'b': 4, 'c': 6 }

console.log(myObject);
// => { 'a': 1, 'b': 2, 'c': 3 }

Array.prototype.reduceréduit un tableau à une seule valeur en fusionnant quelque peu la valeur précédente avec le courant. La chaîne est initialisée par un objet vide {}. À chaque itération, une nouvelle clé de myObjectest ajoutée avec deux fois la clé comme valeur.

Mise à jour

Avec les nouvelles fonctionnalités ES6, il existe une manière plus élégante d'exprimer objectMap.

const objectMap = (obj, fn) =>
  Object.fromEntries(
    Object.entries(obj).map(
      ([k, v], i) => [k, fn(v, k, i)]
    )
  )
  
const myObject = { a: 1, b: 2, c: 3 }

console.log(objectMap(myObject, v => 2 * v)) 

Amberlamps
la source
3
Oui, je ne recommanderais pas ma solution pour ce genre de problème. C'est pourquoi j'ai mis à jour ma réponse avec une solution alternative meilleure.
Amberlamps
14
@KhalilRavanna Je pense que vous avez mal lu le code ici - cette réponse n'utilise pas mapcorrectement parce qu'elle ne fait pas return- elle abuse mapcomme si c'était un forEachappel. S'il le faisait réellement ,return myObject[value] * 2 le résultat serait un tableau contenant les valeurs d'origine doublées, au lieu d'un objet contenant les clés d'origine avec des valeurs doublées, ces dernières étant clairement ce que l'OP demandait.
Alnitak
3
@Amberlamps votre .reduceexemple est OK, mais à mon humble avis , il est encore loin de la commodité d'un .mappour les objets, car votre .reducerappel doit non seulement correspondre à la façon dont cela .reducefonctionne, mais nécessite également qu'il myObjectsoit disponible dans la portée lexicale. Ce dernier en particulier rend impossible de simplement passer une référence de fonction dans le rappel, nécessitant à la place une fonction anonyme sur place.
Alnitak
9
TBH J'aurais plutôt voté pour une réponse qui seule (ou à l'origine) fournissait la deuxième solution présentée dans cette réponse. Le premier fonctionne et n'est pas nécessairement faux, mais comme d'autres l'ont dit, je n'aime pas voir la carte utilisée de cette façon. Quand je vois la carte, mon esprit pense automatiquement "structure de données immuable"
mjohnsonengr
2
.mapn'est pas la méthode appropriée à utiliser lorsque vous n'allez pas utiliser le tableau mappé résultant - si vous ne voulez que des effets secondaires, comme dans votre premier code, vous devriez certainement l'utiliser à la forEachplace.
CertainPerformance
308

Que diriez-vous d'un liner avec affectation variable immédiate en JS ordinaire ( ES6 / ES2015 )?

Utilisation de l' opérateur d'étalement et de la syntaxe du nom de clé calculé :

let newObj = Object.assign({}, ...Object.keys(obj).map(k => ({[k]: obj[k] * obj[k]})));

jsbin

Une autre version utilisant réduire:

let newObj = Object.keys(obj).reduce((p, c) => ({...p, [c]: obj[c] * obj[c]}), {});

jsbin

Premier exemple en fonction:

const oMap = (o, f) => Object.assign({}, ...Object.keys(o).map(k => ({ [k]: f(o[k]) })));

// To square each value you can call it like this:
let mappedObj = oMap(myObj, (x) => x * x);

jsbin

Si vous souhaitez mapper un objet imbriqué récursivement dans un style fonctionnel , cela peut être fait comme ceci:

const sqrObjRecursive = obj =>
  Object.keys(obj).reduce(
    (newObj, key) =>
      obj[key] && typeof obj[key] === "object"
        ? { ...newObj, [key]: sqrObjRecursive(obj[key]) } // recurse.
        : { ...newObj, [key]: obj[key] * obj[key] }, // square val.
    {}
  );       

jsbin

Ou plus impérativement, comme ceci:

const sqrObjRecursive = obj => {
  Object.keys(obj).forEach(key => {
    if (typeof obj[key] === "object") obj[key] = sqrObjRecursive(obj[key]);
    else obj[key] = obj[key] * obj[key];
  });
  return obj;
};

jsbin

Depuis ES7 / ES2016, vous pouvez utiliser Object.entries()au lieu de Object.keys()par exemple comme ceci:

let newObj = Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v})));

ES2019 introduit Object.fromEntries(), ce qui simplifie encore plus ceci:

let newObj = Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * v]));


Propriétés héritées et chaîne de prototypes:

Dans certaines situations rares, vous devrez peut-être mapper un objet de type classe qui détient les propriétés d'un objet hérité sur sa chaîne de prototype . Dans de tels cas Object.keys(), ne fonctionnera pas, car Object.keys()n'énumère pas les propriétés héritées . Si vous devez mapper des propriétés héritées , vous devez utiliser for (key in myObj) {...}.

Voici un exemple d'un objet qui hérite des propriétés d'un autre objet et comment Object.keys()ne fonctionne pas dans un tel scénario.

const obj1 = { 'a': 1, 'b': 2, 'c': 3}
const obj2 = Object.create(obj1);  // One of multiple ways to inherit an object in JS.

// Here you see how the properties of obj1 sit on the 'prototype' of obj2
console.log(obj2)  // Prints: obj2.__proto__ = { 'a': 1, 'b': 2, 'c': 3}

console.log(Object.keys(obj2));  // Prints: an empty Array.

for (key in obj2) {
  console.log(key);              // Prints: 'a', 'b', 'c'
}

jsbin

Cependant, faites-moi plaisir et évitez l' héritage . :-)

Rotareti
la source
1
Magnifique mais j'ai été pris par le fait qu'il Object.keysn'énumère pas les propriétés héritées. Je vous suggère d'ajouter un avertissement.
David Braun
Pour raccourcir votre réponse, utilisez simplement Object.entries({a: 1, b: 2, c: 3}).
1
Vous avez quelques fautes de frappe (vous vous référez à des (o, f)arguments mais utilisez objdans le corps.
kzahel
4
Oui, puis laissez la personne suivante passer quelques minutes à comprendre ce que fait cette fonction? :)
Andrey Popov
1
La solution Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))ne fonctionne pas s'il objs'agit d'un objet vide. Changer à: Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v}))).
blackcatweb
117

Pas de méthodes natives, mais lodash # mapValues fera le travail avec brio

_.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
// → { 'a': 3, 'b': 6, 'c': 9 }
Mario
la source
6
Il n'est pas nécessaire d'ajouter un appel HTTP supplémentaire et une bibliothèque supplémentaire pour une seule fonction. Dans tous les cas, cette réponse est désormais obsolète et vous pouvez simplement appeler Object.entries({a: 1, b: 2, c: 3})pour obtenir un tableau.
11
Pourquoi obtenir un tableau est-il utile? L'exigence était pour un objet mappé. De plus, il n'y a pas d'appel HTTP implicite dans l'utilisation de Lodash, une bibliothèque populaire essentiellement conçue pour ce type de chose, bien qu'il y ait pas mal de choses qu'ES6 a exclu la nécessité d'utiliser des bibliothèques de fonctions utilitaires dans une certaine mesure ces derniers temps.
corse32
2
appel HTTP supplémentaire pour tirer Lodash, je suppose. Si c'est votre seul cas d'utilisation, c'est en fait une surpuissance. Néanmoins, il est logique d'avoir cette réponse autour de Lodash est une bibliothèque si répandue.
igorsantos07
2
sans oublier que vous devez réellement utiliser _.map()pour obtenir la clé comme deuxième argument - comme requis par l'OP.
igorsantos07
_.mapValues()fournit également la clé comme deuxième argument. Aussi _.map()ne fonctionnerait pas ici car il ne retourne que les tableaux, pas des objets:_.map({ 'a': 1, 'b': 2, 'c': 3}, n => n * 3) // [3, 6, 9]
MeltedPenguin
57

Il est assez facile d'en écrire un:

Object.map = function(o, f, ctx) {
    ctx = ctx || this;
    var result = {};
    Object.keys(o).forEach(function(k) {
        result[k] = f.call(ctx, o[k], k, o); 
    });
    return result;
}

avec un exemple de code:

> o = { a: 1, b: 2, c: 3 };
> r = Object.map(o, function(v, k, o) {
     return v * v;
  });
> r
{ a : 1, b: 4, c: 9 }

NB: cette version vous permet également de (éventuellement) définir le thiscontexte du rappel, tout comme la Arrayméthode.

EDIT - modifié pour supprimer l'utilisation de Object.prototype, afin de s'assurer qu'il ne se heurte à aucune propriété existante nommée mapsur l'objet.

Alnitak
la source
3
D'accord. :) Un commentaire sur cette question m'a amené ici. Je vais attribuer +1 à cela car la réponse acceptée est clairement une mauvaise utilisation map, mais je dois dire que la modification Object.prototypene me convient pas, même si elle n'est pas énumérable.
JLRishe
2
@JLRishe Merci. À mon humble avis dans ES5, il n'y a vraiment plus de raison de ne pas modifier Object.prototypeautre que le risque (théorique) de collision avec d'autres méthodes. FWIW, je ne peux pas comprendre comment l'autre réponse a obtenu autant de votes qu'elle a, c'est complètement faux.
Alnitak
7
@JLRishe, vous êtes un vieux geezer ... faites le temps et modifiez ce prototype!
Nick Manning
12
@NickManning Vous jeunes whippersnappers et votre comportement irresponsable. Descendre ma pelouse!
JLRishe
3
Ce déjà ne fonctionne pas: o = {map: true, image: false}. Vous risquer juste pour écrire au o.map(fn)lieu demap(o,fn)
fregante
25

Vous pouvez utiliser Object.keyspuisforEach sur le tableau de clés retourné:

var myObject = { 'a': 1, 'b': 2, 'c': 3 },
    newObject = {};
Object.keys(myObject).forEach(function (key) {
    var value = myObject[key];
    newObject[key] = value * value;
});

Ou de façon plus modulaire:

function map(obj, callback) {
    var result = {};
    Object.keys(obj).forEach(function (key) {
        result[key] = callback.call(obj, obj[key], key, obj);
    });
    return result;
}

newObject = map(myObject, function(x) { return x * x; });

Notez que Object.keysrenvoie un tableau contenant uniquement les propres propriétés énumérables de l'objet, donc il se comporte comme une for..inboucle avec une hasOwnPropertyvérification.

Mattias Buelens
la source
20

C'est bs, et tout le monde dans la communauté JS le sait. Il devrait y avoir cette fonctionnalité:

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}

voici l'implémentation naïve:

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;
};

c'est super ennuyeux d'avoir à implémenter ça tout le temps;)

Si vous voulez quelque chose d'un peu plus sophistiqué, qui n'interfère pas avec la classe Object, essayez ceci:

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;
});

mais il est sûr d'ajouter cette fonction de carte à Object, ne l'ajoutez pas à Object.prototype.

Object.map = ... // fairly safe
Object.prototype.map ... // not ok
Alexander Mills
la source
Pourquoi ajoutez-vous cette fonction à l' Objectobjet global ? Juste const mapObject = (obj, fn) => { [...]; return ret; }.
Amberlamps
7
Parce que ça devrait être une méthode sur Object :)
Alexander Mills
1
Tant que nous ne plaisantons pas avec Object.prototype, nous devrions être d'accord
Alexander Mills
1
Si la carte n'était qu'une chose vague et aléatoire, bien sûr. Mais .mapest généralement compris comme une interface Functor, et il n'y a pas de définition unique de Functor pour "Objet" car pratiquement tout ce qui est en javascript peut être un Objet, y compris des choses avec leurs propres interfaces / logiques .map très distinctes et définies.
Dtipson
1
À mon avis, le premier argument de rappel devrait être l'élément itéré lui-même, pas une clé. Par exemple, je suppose qu'il devrait me donner le même objet, comme cela arrive habituellement avec la carte commune: Object.map ({prop: 1}, el => el) mais il renvoie des clés avec la valeur du même nom de clé ...
Gleb Dolzikov
17

Je suis venu ici pour trouver et répondre pour mapper un objet sur un tableau et j'ai obtenu cette page en conséquence. Si vous êtes venu ici à la recherche de la même réponse que moi, voici comment vous pouvez mapper et vous opposer à un tableau.

Vous pouvez utiliser map pour renvoyer un nouveau tableau à partir de l'objet comme ceci:

var newObject = Object.keys(myObject).map(function(key) {
   return myObject[key];
});
JoeTron
la source
6
Cela ne fonctionne pas à distance - il renvoie simplement un tableau des valeurs de l'objet, pas un nouvel objet. Je ne peux pas croire qu'il ait autant de votes positifs.
Alnitak
1
Vous avez raison, il ne répond pas correctement à la question car ils cherchaient une réponse pour mapper un objet à un objet. Je suis venu ici à la recherche d'une réponse pour mapper un objet sur un tableau et j'ai obtenu cette page en conséquence. à la recherche de la mise en correspondance d'un objet avec des tableaux.
JoeTron
5
Dans ES7, ce sera trivialObject.values(myObject)
Alnitak
1
C'est agréable à savoir, mais certaines personnes doivent encore utiliser le code hérité.
JoeTron
const obj = {foo: 'bar', baz: 42}; console.log (Object.entries (obj)); // [['foo', 'bar'], ['baz', 42]]
Bashirpour
12

La réponse acceptée présente deux inconvénients:

  • Il abuse Array.prototype.reduce, car réduire signifie changer la structure d'un type composite, ce qui ne se produit pas dans ce cas.
  • Il n'est pas particulièrement réutilisable

Une approche fonctionnelle ES6 / ES2015

Veuillez noter que toutes les fonctions sont définies sous forme curry.

// 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));

  • Dans la ligne A oest réaffecté. Puisque Javascript transmet les valeurs de référence par partage , une copie superficielle de oest générée. Nous pouvons maintenant muter à l' ointérieur omapsans mutero dans la portée parent.
  • Dans la ligne B, mapla valeur de retour est ignorée, car mapeffectue une mutation de o. Étant donné que cet effet secondaire reste à l'intérieur omapet n'est pas visible dans la portée parent, il est totalement acceptable.

Ce n'est pas la solution la plus rapide, mais une solution déclarative et réutilisable. Voici la même implémentation qu'une ligne, succincte mais moins lisible:

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

Addendum - pourquoi les objets ne sont-ils pas itérables par défaut?

ES2015 a spécifié l'itérateur et les protocoles itérables. Mais les objets ne sont toujours pas itérables et donc non mappables. La raison en est le mélange des données et du niveau du programme .

Communauté
la source
1
On dirait un peu trop conçu; Je compte 6 fonctions pour ce qui est essentiellement une boucle. D'autres solutions sont beaucoup plus simples et la réponse acceptée est également 5 fois plus rapide.
fregante
4
@ bfred.it Quel est le but de votre commentaire? Si vous aimez la micro-optimisation, vous ne devriez pas utiliser non Array.prototype.reduceplus. Tout ce que je veux illustrer ici, c'est que la réponse acceptée est en quelque sorte «des abus» Array.prototype.reduceet comment une cartographie purement fonctionnelle pourrait être mise en œuvre. Si vous n'êtes pas intéressé par la programmation fonctionnelle, ignorez simplement ma réponse.
2
"réduire signifie changer la structure d'un type composite", je ne pense pas que ce soit vrai. Un Array.reduce qui produit un nouveau tableau de la même longueur exacte ou même des mêmes valeurs est parfaitement légitime. Réduire peut être utilisé pour recréer la fonctionnalité de la carte et / ou du filtre (ou les deux, comme dans un transducteur complexe, dont la réduction est la base). Dans FP, c'est un type de pli, et un pli qui se retrouve avec le même type est toujours un pli.
Dtipson
2
@Dtipson Vous avez absolument raison. A foldest plus générique qu'un map(foncteur). Cependant, c'était ma connaissance de la mi-2016. C'est une demi-éternité: D
1
Ha, ouais. Il m'a fallu des années pour réaliser à quel point toutes ces choses sont interconnectées et l'écosystème plus large des termes et des interfaces. Les choses qui semblent être si faciles se révèlent être incroyablement complexes, et certaines choses qui semblent incroyablement complexes résument parfaitement. :)
Dtipson
10

JavaScript vient de recevoir la nouvelle Object.fromEntriesméthode.

Exemple

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)

Explication

Le code ci-dessus convertit l'objet en un tableau imbriqué ( [[<key>,<value>], ...]) sur lequel vous pouvez mapper. Object.fromEntriesreconvertit le tableau en objet.

La chose intéressante à propos de ce modèle, c'est que vous pouvez maintenant facilement prendre en compte les clés d'objet lors du mappage.

Documentation

Prise en charge du navigateur

Object.fromEntriesn'est actuellement pris en charge que par ces navigateurs / moteurs , néanmoins il existe des polyfills disponibles (par exemple @ babel / polyfill ).

HaNdTriX
la source
2
Fondamentalement, Object.fromEntriesc'est l'opposé de Object.entries. Object.entriesconvertit un objet en une liste de paires clé-valeur. Object.fromEntriesconvertit une liste de paires clé-valeur en un objet.
orad
8

Version minimale (es6):

Object.entries(obj).reduce((a, [k, v]) => (a[k] = v * v, a), {})
Yukulélé
la source
Je pense que c'est incorrect à cause d'une faute de frappe, je pense que vous voulez dire => Object.entries(obj).reduce((a, [k, v]) => [a[k] = v * v, a], {}), crochets angulaires au lieu de parenthèses.
Alexander Mills
@AlexanderMills, mon code est correct. En savoir plus sur l'opérateur virgule entre parenthèses: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Yukulélé
compris, vous voudrez peut-être expliquer cela la prochaine fois, mais je suppose que c'est à cela que servent les commentaires.
Alexander Mills
1
en gros, mon code est équivalent à:Object.entries(obj).reduce((a, [k, v]) => {a[k] = v * v; return a}, {})
Yukulélé
1
Très bonne réponse. Object.entries().reduceest la meilleure solution , je pense avec ES6 + .Would obtenir plus upvotes si elle a utilisé une returndéclaration dans le réducteur plutôt que implicit returnet l' commaopérateur - qui est propre mais difficile à lire à mon humble avis
Drenai
7

Pour des performances maximales.

Si votre objet ne change pas souvent mais doit être itéré souvent, je suggère d'utiliser une carte native comme cache.

// example object
var obj = {a: 1, b: 2, c: 'something'};

// caching map
var objMap = new Map(Object.entries(obj));

// fast iteration on Map object
objMap.forEach((item, key) => {
  // do something with an item
  console.log(key, item);
});

Object.entries fonctionne déjà dans Chrome, Edge, Firefox et beta Opera, c'est donc une fonctionnalité évolutive. C'est de ES7 donc polyfill il https://github.com/es-shims/Object.entries pour IE où cela ne fonctionne pas.

Pawel
la source
Que diriez-vous d'utiliser la destruction ?
Константин Ван
1
@ K._ c'était très lent, mais je ne connais pas les performances maintenant. Il semble que V8 v 5.6 ait introduit quelques optimisations pour la déstructuration mais cela doit être mesuré v8project.blogspot.co.uk
Pawel
Ou, es7 moins performant mais immuable:const fn = v => v * 2; const newObj = Object.entries(myObject).reduce((acc, [k,v]) => Object.assign({}, acc, {[k]: fn(v)}), {});
mikebridge
Je me demande pourquoi aucune des autres réponses n'utilise Object.entries. C'est tellement plus propre que d'itérer sur des touches et d'utiliser obj [touche].
corwin.amber
6

vous pouvez utiliser la mapméthode et forEachsur les tableaux mais si vous souhaitez l'utiliser, Objectvous pouvez l'utiliser avec une torsion comme ci-dessous:

Utilisation de Javascript (ES6)

var obj = { 'a': 2, 'b': 4, 'c': 6 };   
Object.entries(obj).map( v => obj[v[0]] *= v[1] );
console.log(obj); //it will log as {a: 4, b: 16, c: 36}

var obj2 = { 'a': 4, 'b': 8, 'c': 10 };
Object.entries(obj2).forEach( v => obj2[v[0]] *= v[1] );
console.log(obj2); //it will log as {a: 16, b: 64, c: 100}

Utilisation de jQuery

var ob = { 'a': 2, 'b': 4, 'c': 6 };
$.map(ob, function (val, key) {
   ob[key] *= val;
});
console.log(ob) //it will log as {a: 4, b: 16, c: 36}

Ou vous pouvez également utiliser d'autres boucles comme $.each méthode comme dans l'exemple ci-dessous:

$.each(ob,function (key, value) {
  ob[key] *= value;
});
console.log(ob) //it will also log as {a: 4, b: 16, c: 36}
Haritsinh Gohil
la source
$est jquery?
masterxilo
Oui, vous pouvez utiliser à la fois $ et jQuery
Haritsinh Gohil
1
@Ronald j'ai mal compris, voir ma réponse mise à jour, merci de ne pas avoir baissé les votes mais d'avoir vraiment pris conscience de mon erreur, merci d'avoir amélioré la communauté.
Haritsinh Gohil
@bcoughlan oui c'est exactement ce que j'ai aussi écrit en commentaire, c'est la quadrature des chiffres, que voulez-vous alors?
Haritsinh Gohil
1
@HaritsinhGohil J'ai compris que cela vous critiquait d'avoir donné une solution jQuery. Il est vrai qu'il y a un élan ces jours-ci pour s'éloigner de jQuery, et bien sûr il y a Node, mais à moins qu'un OP ne demande spécifiquement JS ou Node pur, je pense que c'est bien de supposer que quelqu'un travaille dans un environnement web et que jQuery est presque certain d'être disponible.
abalter
4

Le map functionn'existe pas sur le Object.prototypemais vous pouvez l'imiter comme ça

var myMap = function ( obj, callback ) {

    var result = {};

    for ( var key in obj ) {
        if ( Object.prototype.hasOwnProperty.call( obj, key ) ) {
            if ( typeof callback === 'function' ) {
                result[ key ] = callback.call( obj, obj[ key ], key, obj );
            }
        }
    }

    return result;

};

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

var newObject = myMap( myObject, function ( value, key ) {
    return value * value;
});
whitneyit
la source
Problèmes de performances: typeof callback === 'function'absolument redondants. 1) mapn'a pas de sens sans rappel 2) si callbackn'est pas une fonction, votre mapimplémentation s'exécute simplement sans signification pour la boucle. En outre, Object.prototype.hasOwnProperty.call( obj, key )c'est un peu exagéré.
ankhzet
3

Je suis tombé sur cela en tant que premier élément d'une recherche Google essayant d'apprendre à le faire, et j'ai pensé partager avec d'autres personnes qui trouveraient récemment la solution que j'ai trouvée, qui utilise le package npm immuable.

Je pense qu'il est intéressant de partager car immutable utilise la situation EXACT de l'OP dans sa propre documentation - ce qui suit n'est pas mon propre code mais tiré de la documentation immuable-js actuelle:

const { Seq } = require('immutable')
const myObject = { a: 1, b: 2, c: 3 }
Seq(myObject).map(x => x * x).toObject();
// { a: 1, b: 4, c: 9 } 

Non pas que Seq ait d'autres propriétés ("Seq décrit une opération paresseuse, leur permettant de chaîner efficacement l'utilisation de toutes les méthodes de collecte d'ordre supérieur (telles que la carte et le filtre) en ne créant pas de collections intermédiaires") et que certaines autres données immuable-js les structures pourraient également faire le travail assez efficacement.

Quiconque utilise cette méthode devra bien sûr npm install immutableet voudra peut-être lire les documents:

https://facebook.github.io/immutable-js/

alex.h
la source
3

EDIT: La manière canonique d'utiliser les nouvelles fonctionnalités JavaScript est -

const identity = x =>
  x

const omap = (f = identity, o = {}) =>
  Object.fromEntries(
    Object.entries(o).map(([ k, v ]) =>
      [ k, f(v) ]
    )
  )

oest un objet et fest votre fonction de mappage. Ou nous pourrions dire, étant donné une fonction de a -> b, et un objet avec des valeurs de type a, produire un objet avec des valeurs de type b. En tant que signature de pseudo-type -

// omap : (a -> b, { a }) -> { b }

La réponse originale a été écrite pour démontrer un puissant combinateur, mapReducequi nous permet de penser notre transformation d'une manière différente

  1. m, la fonction de mappage - vous donne une chance de transformer l'élément entrant avant…
  2. r, la fonction de réduction - cette fonction combine l'accumulateur avec le résultat de l'élément mappé

Intuitivement, mapReducecrée un nouveau réducteur auquel nous pouvons nous connecter directement Array.prototype.reduce. Mais plus important encore, nous pouvons implémenter omapclairement notre implémentation de foncteur d'objet en utilisant le monoïde objet, Object.assignet {}.

const identity = x =>
  x
  
const mapReduce = (m, r) =>
  (a, x) => r (a, m (x))

const omap = (f = identity, o = {}) =>
  Object
    .keys (o)
    .reduce
      ( mapReduce
          ( k => ({ [k]: f (o[k]) })
          , Object.assign
          )
      , {}
      )
          
const square = x =>
  x * x
  
const data =
  { a : 1, b : 2, c : 3 }
  
console .log (omap (square, data))
// { a : 1, b : 4, c : 9 }

Remarquez que la seule partie du programme que nous avons dû écrire est l'implémentation de la cartographie elle-même -

k => ({ [k]: f (o[k]) })

Qui dit, étant donné un objet connu oet une clé k, construire un objet et dont la propriété calculée kest le résultat de l'appel fà la valeur de la clé,o[k] .

Nous obtenons un aperçu du mapReducepotentiel de séquençage de si nous faisons d'abord un résuméoreduce

// oreduce : (string * a -> string * b, b, { a }) -> { b }
const oreduce = (f = identity, r = null, o = {}) =>
  Object
    .keys (o)
    .reduce
      ( mapReduce
          ( k => [ k, o[k] ]
          , f
          )
      , r
      )

// omap : (a -> b, {a}) -> {b}
const omap = (f = identity, o = {}) =>
  oreduce
    ( mapReduce
        ( ([ k, v ]) =>
            ({ [k]: f (v) })
        , Object.assign
        )
    , {}
    , o
    )

Tout fonctionne de la même manière, mais omappeut maintenant être défini à un niveau supérieur. Bien sûr, le nouveauObject.entries rend cela ridicule, mais l'exercice est toujours important pour l'apprenant.

Vous ne verrez pas tout le potentiel d' mapReduceici, mais je partage cette réponse car il est intéressant de voir combien d'endroits peuvent être appliqués. Si vous êtes intéressé par la façon dont il est dérivé et par d'autres moyens qui pourraient être utiles, veuillez consulter cette réponse .

Je vous remercie
la source
4
Utilisons plutôt Mapet Map.entries, OK: D. Je suis fatigué d'abuser des objets simples comme types de données cartographiques.
2
Je suis d'accord Mapdevrait être utilisé où / quand c'est possible, mais cela ne remplace pas totalement la nécessité d'utiliser ces procédures sur des objets JS simples tho: D
Merci
2

Basé sur la réponse @Amberlamps, voici une fonction utilitaire (en tant que commentaire, elle avait l'air moche)

function mapObject(obj, mapFunc){
    return Object.keys(obj).reduce(function(newObj, value) {
        newObj[value] = mapFunc(obj[value]);
        return newObj;
    }, {});
}

et l'utilisation est:

var obj = {a:1, b:3, c:5}
function double(x){return x * 2}

var newObj = mapObject(obj, double);
//=>  {a: 2, b: 6, c: 10}
yonatanmn
la source
2
var myObject = { 'a': 1, 'b': 2, 'c': 3 };


Object.prototype.map = function(fn){
    var oReturn = {};
    for (sCurObjectPropertyName in this) {
        oReturn[sCurObjectPropertyName] = fn(this[sCurObjectPropertyName], sCurObjectPropertyName);
    }
    return oReturn;
}
Object.defineProperty(Object.prototype,'map',{enumerable:false});





newObject = myObject.map(function (value, label) {
    return value * value;
});


// newObject is now { 'a': 1, 'b': 4, 'c': 9 }
Dmitry Ragozin
la source
enumerablepar défautfalse
Merci
2

Ma réponse est largement basée sur la réponse la mieux notée ici et j'espère que tout le monde comprend (ayez la même explication sur mon GitHub aussi). C'est pourquoi son implémentation avec la carte fonctionne:

Object.keys(images).map((key) => images[key] = 'url(' + '"' + images[key] + '"' +    
')');

Le but de la fonction est de prendre un objet et de modifier le contenu d'origine de l'objet en utilisant une méthode disponible pour tous les objets (objets et tableaux de la même manière) sans retourner de tableau. Presque tout dans JS est un objet, et pour cette raison, des éléments plus bas dans le pipeline d'héritage peuvent potentiellement techniquement utiliser ceux disponibles pour ceux qui sont en amont (et l'inverse, il apparaît).

La raison pour laquelle cela fonctionne est due aux fonctions .map renvoyant un tableau EXIGEANT que vous fournissiez un RETOUR explicite ou implicite d'un tableau au lieu de simplement modifier un objet existant. Vous trompez essentiellement le programme en pensant que l'objet est un tableau en utilisant Object.keys qui vous permettra d'utiliser la fonction de carte avec son action sur les valeurs auxquelles les clés individuelles sont associées (j'ai en fait renvoyé des tableaux par erreur mais je l'ai corrigé). Tant qu'il n'y a pas de retour dans le sens normal, il n'y aura pas de tableau créé avec l'objet d'origine toujours intact et modifié comme programmé.

Ce programme particulier prend un objet appelé images et prend les valeurs de ses clés et ajoute des balises url à utiliser dans une autre fonction. L'original est le suivant:

var images = { 
snow: 'https://www.trbimg.com/img-5aa059f5/turbine/bs-md-weather-20180305', 
sunny: 'http://www.cubaweather.org/images/weather-photos/large/Sunny-morning-east-   
Matanzas-city- Cuba-20170131-1080.jpg', 
rain: 'https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg' };

... et modifié est ceci:

var images = { 
snow: url('https://www.trbimg.com/img-5aa059f5/turbine/bs-md-weather-20180305'),     
sunny: url('http://www.cubaweather.org/images/weather-photos/large/Sunny-morning-   
east-Matanzas-city- Cuba-20170131-1080.jpg'), 
rain: url('https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg') 
};

La structure d'origine de l'objet est laissée intacte, ce qui permet un accès normal à la propriété tant qu'il n'y a pas de retour. Ne le faites pas renvoyer un tableau comme d'habitude et tout ira bien. Le but est de RÉASSIGNER les valeurs originales (images [clé]) à ce qui est voulu et pas à autre chose. Pour autant que je sache, afin d'empêcher la sortie du tableau, il DOIT y avoir RÉASSIGNATION des images [clé] et aucune demande implicite ou explicite de renvoyer un tableau (l'affectation des variables fait cela et me faisait des allers-retours).

ÉDITER:

Je vais aborder son autre méthode concernant la création de nouveaux objets pour éviter de modifier l'objet d'origine (et une réaffectation semble toujours nécessaire pour éviter de créer accidentellement un tableau en sortie). Ces fonctions utilisent la syntaxe des flèches et le sont si vous voulez simplement créer un nouvel objet pour une utilisation future.

const mapper = (obj, mapFn) => Object.keys(obj).reduce((result, key) => {
                result[key] = mapFn(obj)[key];
                return result;
            }, {});

var newImages = mapper(images, (value) => value);

Le fonctionnement de ces fonctions est le suivant:

mapFn prend la fonction à ajouter plus tard (dans ce cas (valeur) => valeur) et renvoie simplement tout ce qui y est stocké comme valeur pour cette clé (ou multiplié par deux si vous modifiez la valeur de retour comme il l'a fait) dans mapFn ( obj) [clé],

puis redéfinit la valeur d'origine associée à la clé dans result [key] = mapFn (obj) [key]

et renvoie l'opération effectuée sur le résultat (l'accumulateur situé dans les parenthèses initiée à la fin de la fonction .reduce).

Tout cela est effectué sur l'objet choisi et TOUJOURS IL NE PEUT PAS y avoir de demande implicite pour un tableau retourné et ne fonctionne que lors de la réaffectation de valeurs pour autant que je sache. Cela nécessite une certaine gymnastique mentale, mais réduit les lignes de code nécessaires, comme on peut le voir ci-dessus. La sortie est exactement la même que celle illustrée ci-dessous:

{snow: "https://www.trbimg.com/img-5aa059f5/turbine/bs-   
md-weather-20180305", sunny: "http://www.cubaweather.org/images/weather-
photos/lmorning-east-Matanzas-city-Cuba-20170131-1080.jpg", rain: 
"https://i.pinimg.com/originals/23/d8
/ab/23d8ab1eebc72a123cebc80ce32b43d8.jpg"}

Gardez à l'esprit que cela a fonctionné avec NON-NUMBERS. Vous POUVEZ dupliquer N'IMPORTE QUEL objet en RETOURNANT SIMPLEMENT LA VALEUR dans la fonction mapFN.

Andrew Ramshaw
la source
2

La première fonction répond à la question. Il crée une carte.

La deuxième fonction ne crée pas de carte. Au lieu de cela, il parcourt les propriétés de l'objet existant à l'aide de hasOwnProperty(). Cette alternative de carte peut être une meilleure solution dans certaines situations.

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


//creates a map (answers question, but function below is 
//much better in some situations)
var newObject = {};
makeMap();    
function makeMap() {
    for (var k in myObject) {
        var value = myObject[k];
        newObject[k] = value * value;
    }
    console.log(newObject); //mapped array
}


//Doesn't create a map, just applies the function to
//a specific property using existing data
function getValue(key) {
  for (var k in myObject) {
    if (myObject.hasOwnProperty(key)) {
      var value = myObject[key]
      return value * value; //stops iteration
    }
  }
}
Input: <input id="input" value="" placeholder="a, b or c"><br>
Output:<input id="output"><br>
<button onclick="output.value=getValue(input.value)" >Get value</button>

Victor Stoddard
la source
2
Cela ne répond pas à la question d'origine - il ne s'agit pas de mapper les valeurs à un nouvel objet avec une fonction fournie (c'est-à-dire que cela n'a rien à voir avec Array.prototype.map).
Matt Browne
@MattBrowne J'ai ajouté une deuxième méthode qui crée ce qui est sans doute une carte. C'est lent, mais fait ce que le PO demande. La première méthode est idéale dans de nombreuses situations, c'est pourquoi j'ai utilisé une boucle et des fonctions de propriété.
Victor Stoddard
1

Si vous êtes intéressé à mapping non seulement des valeurs mais aussi des clés, j'ai écrit Object.map(valueMapper, keyMapper), cela se comporte de cette façon:

var source = { a: 1, b: 2 };
function sum(x) { return x + x }

source.map(sum);            // returns { a: 2, b: 4 }
source.map(undefined, sum); // returns { aa: 1, bb: 2 }
source.map(sum, sum);       // returns { aa: 2, bb: 4 }
MattiSG
la source
3
Je suppose que vous avez simplement fourni un lien comme réponse, ce qui est mal vu.
Chris Wright
Si jamais utile pour tout le monde: npm install @mattisg/object.map.
MattiSG
1

J'avais besoin d'une version qui permettait également de modifier les clés (basée sur les réponses @Amberlamps et @yonatanmn);

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: légère modification à passer dans l'objet de départ {}. Permet qu'il soit [] (si les clés sont des entiers)

Symétrique
la source
1

Je voulais spécifiquement utiliser la même fonction que j'utilisais pour les tableaux pour un seul objet, et je voulais rester simple. Cela a fonctionné pour moi:

var mapped = [item].map(myMapFunction).pop();
Jason Norwood-Young
la source
ce n'est pas différent devar mapped = myMapFunction(item)
Merci
C'est tellement simple que toutes les autres solutions de "science-fiction" que j'ai vues ici. Merci pour le partage :)
Yuri Teixeira
1

Pour répondre de plus près à ce que le PO a précisément demandé, le PO veut un objet:

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

d'avoir une méthode de carte myObject.map,

similaire à Array.prototype.map qui serait utilisé comme suit:

newObject = myObject.map (fonction (valeur, étiquette) {
    valeur de retour * valeur;
});
// newObject est maintenant {'a': 1, 'b': 4, 'c': 9}

La meilleure réponse (mesurée en termes de " proche de ce qui est demandé " + "aucun ES {5,6,7} requis inutilement") serait:

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;
}

Le code ci-dessus évite d'utiliser intentionnellement les fonctionnalités de langage, uniquement disponible dans les éditions récentes d'ECMAScript. Avec le code ci-dessus, le problème peut être résolu lke ceci:

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);
code de test alternatif ici

En plus d'être mal vu par certains, ce serait une possibilité d'insérer la solution dans la chaîne prototype comme celle-ci.

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;
}

Quelque chose qui, lorsqu'il est effectué avec une surveillance attentive, ne devrait avoir aucun effet néfaste et ne pas avoir d'incidence sur la mapméthode d'autres objets (c'est-à-dire les tableaux map).

humanANDpeace
la source
1

Définissez une fonction mapEntries.

mapEntries prend une fonction de rappel qui est appelée sur chaque entrée de l'objet avec la valeur des paramètres, la clé et l'objet. Il doit renvoyer une nouvelle valeur.

mapEntries doit renvoyer un nouvel objet avec les nouvelles valeurs renvoyées par le rappel.

Object.defineProperty(Object.prototype, 'mapEntries', {
  enumerable: false,
  value: function (mapEntriesCallback) {
    return Object.fromEntries(
      Object.entries(this).map(
        ([key, value]) => [key, mapEntriesCallback(value, key, this)]
      )
    )
  }
})


// Usage example:

var object = {a: 1, b: 2, c: 3}
var newObject = object.mapEntries(value => value * value)
console.log(newObject)
//> {a: 1, b: 4, c: 9}

Edit: une version précédente ne spécifiait pas que ce n'était pas une propriété énumérable

Arye Eidelman
la source
0

Hey a écrit une petite fonction de mappeur qui pourrait aider.

    function propertyMapper(object, src){
         for (var property in object) {   
           for (var sourceProp in src) {
               if(property === sourceProp){
                 if(Object.prototype.toString.call( property ) === '[object Array]'){
                   propertyMapper(object[property], src[sourceProp]);
                   }else{
                   object[property] = src[sourceProp];
                }
              }
            }
         }
      }
Zak
la source
1
Je suppose que vous vouliez dire "je" au lieu de "hé"
Carlo
0

Une autre approche consiste à utiliser une fonction json stringify personnalisée qui peut également fonctionner sur des objets profonds. Cela peut être utile si vous avez l'intention de le publier sur le serveur en tant que json

const obj = { 'a': 1, 'b': 2, x: {'c': 3 }}
const json = JSON.stringify(obj, (k, v) => typeof v === 'number' ? v * v : v)

console.log(json)
console.log('back to json:', JSON.parse(json))

Interminable
la source
0

Je gère uniquement les chaînes pour réduire les exemptions:

Object.keys(params).map(k => typeof params[k] == "string" ? params[k] = params[k].trim() : null);
Idan
la source
0

Mappeur d'objets dans TypeScript

J'aime les exemples qui utilisent Object.fromEntriescomme celui-ci , mais quand même, ils ne sont pas très faciles à utiliser. Les réponses qui utilisent Object.keyspuis recherchent le keyfont en fait plusieurs recherches qui peuvent ne pas être nécessaires.

Je souhaitais qu'il y ait une Object.mapfonction, mais nous pouvons créer la nôtre et l'appeler objectMapavec la possibilité de modifier les deux keyetvalue :

Utilisation (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;
}

Si vous n'utilisez pas TypeScript, copiez le code ci-dessus dans TypeScript Playground pour obtenir le code JavaScript.

En outre, la raison pour laquelle je l' ai mis keySelectoraprèsvalSelector dans la liste des paramètres, c'est parce que c'est facultatif.

* Un certain mérite revient à la réponse d'Alexander-Mills .

orad
la source
0
const mapObject = (targetObject, callbackFn) => {
    if (!targetObject) return targetObject;
    if (Array.isArray(targetObject)){
        return targetObject.map((v)=>mapObject(v, callbackFn))
    }
    return Object.entries(targetObject).reduce((acc,[key, value]) => {
        const res = callbackFn(key, value);
        if (!Array.isArray(res) && typeof res ==='object'){
            return {...acc, [key]: mapObject(res, callbackFn)}
        }
        if (Array.isArray(res)){
            return {...acc, [key]: res.map((v)=>mapObject(v, callbackFn))}
        }
        return {...acc, [key]: res};
    },{})
};
const mapped = mapObject(a,(key,value)=> {
    if (!Array.isArray(value) && key === 'a') return ;
    if (!Array.isArray(value) && key === 'e') return [];
    if (!Array.isArray(value) && key === 'g') return value * value;
    return value;
});
console.log(JSON.stringify(mapped)); 
// {"b":2,"c":[{"d":2,"e":[],"f":[{"g":4}]}]}

Cette fonction parcourt récursivement l'objet et les tableaux d'objets. Les attributs peuvent être supprimés s'ils sont renvoyés non définis

Stanislav
la source