función de mapa para objetos (en lugar de matrices)

1068

Tengo un objeto:

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

Estoy buscando un método nativo, similar al Array.prototype.mapque se usaría de la siguiente manera:

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

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

¿JavaScript tiene esa mapfunción para los objetos? (Quiero esto para Node.JS, por lo que no me importan los problemas entre navegadores).

Randomblue
fuente
1
La mayoría de las respuestas usan Object.keys, que no tiene un orden bien definido. Eso puede ser problemático, sugiero usar en su Object.getOwnPropertyNameslugar.
Oriol
14
Es sorprendente para mí que JS no haya previsto esta tarea increíblemente rudimentaria.
jchook
3
@ Oriol, ¿estás seguro de eso? Según los documentos web de MDN, el orden de los elementos de la matriz es coherente entre Object.keysy Object.getOwnPropertyNames. Ver developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Bart
@Bart El orden de Object.keysdepende de la implementación. Ver stackoverflow.com/a/30919039/1529630
Oriol
44
@ Oriol No debes confiar en el orden de las teclas en los objetos de todos modos, por lo que es un poco irrelevante para la pregunta, ya que nunca especificó que el orden le importaba. Y si el orden le importaba, no debería estar usando un objeto de todos modos.
BT

Respuestas:

1545

No hay nativos map para el Objectobjeto, pero ¿qué tal esto:

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 }

Pero podría iterar fácilmente sobre un objeto usando 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 }

Actualizar

Mucha gente menciona que los métodos anteriores no devuelven un nuevo objeto, sino que operan en el objeto mismo. Para el caso, quería agregar otra solución que devuelva un nuevo objeto y deje el objeto original como 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.reducereduce una matriz a un solo valor al fusionar un poco el valor anterior con el actual. La cadena se inicializa con un objeto vacío {}. En cada iteración myObjectse agrega una nueva clave de con el doble de la clave como valor.

Actualizar

Con las nuevas características de ES6, hay una forma más elegante de expresar 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)) 

Lámparas de color ambar
fuente
3
Sí, no recomendaría mi solución para ese tipo de problema. Por eso actualicé mi respuesta con una solución alternativa y mejor.
Amberlamps
14
@KhalilRavanna Creo que has leído mal el código aquí, esta respuesta no se está utilizando mapcorrectamente porque no está haciendo un returnabuso, es abusivo mapcomo si fuera una forEachllamada. Si realmente lo hizo, return myObject[value] * 2 entonces el resultado sería una matriz que contiene los valores originales duplicados, en lugar de un objeto que contiene las claves originales con valores duplicados, siendo este último claramente lo que solicitó el OP.
Alnitak
3
@Amberlamps su .reduceejemplo está bien, pero en mi humilde opinión, todavía está muy lejos de la conveniencia de un .mappara objetos, ya que su .reducedevolución de llamada no solo tiene que coincidir con la forma en que .reducefunciona, sino que también requiere que myObjectesté disponible en el ámbito léxico. Esto último en particular hace que sea imposible pasar una referencia de función en la devolución de llamada, lo que requiere una función anónima en su lugar.
Alnitak
99
TBH Hubiera votado una respuesta que solo (u originalmente) proporcionó la segunda solución presentada en esta respuesta. El primero funciona y no es necesariamente incorrecto, pero como han dicho otros, no me gusta ver que el mapa se use de esa manera. Cuando veo el mapa, mi mente está pensando automáticamente en "estructura de datos inmutable"
mjohnsonengr
2
.mapno es el método apropiado para usar cuando no va a usar la matriz asignada resultante; si solo desea efectos secundarios, como en su primer código, definitivamente debería usarlo forEachen su lugar.
CertainPerformance
308

¿Qué tal una línea con asignación de variable inmediata en JS simple ( ES6 / ES2015 )?

Utilizando el operador de propagación y la sintaxis de nombre de clave calculada :

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

jsbin

Otra versión que usa reduce:

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

jsbin

Primer ejemplo como función:

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 desea asignar un objeto anidado de forma recursiva en un estilo funcional , puede hacerlo así:

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

O más imperativamente, así:

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

Desde ES7 / ES2016 puede usar en Object.entries()lugar de, Object.keys()por ejemplo, así:

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

Se introdujo ES2019Object.fromEntries() , lo que simplifica esto aún más:

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


Propiedades heredadas y la cadena del prototipo:

En alguna situación rara, es posible que deba asignar un objeto de clase que tenga propiedades de un objeto heredado en su cadena de prototipo . En tales casos Object.keys()no funcionará, porque Object.keys()no enumera las propiedades heredadas . Si necesita asignar propiedades heredadas , debe usarfor (key in myObj) {...} .

Aquí hay un ejemplo de un objeto que hereda las propiedades de otro objeto y cómo Object.keys()no funciona en ese escenario.

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

Sin embargo, por favor, hágame un favor y evite la herencia . :-)

Rotareti
fuente
1
Hermoso pero quedé atrapado por el hecho de que Object.keysno enumera las propiedades heredadas. Te sugiero que agregues una advertencia.
David Braun
Para acortar su respuesta, solo use Object.entries({a: 1, b: 2, c: 3}).
1
Usted tiene algunos errores ortográficos (que se refieren a (o, f)como argumentos, pero su uso objen el cuerpo.
kzahel
44
Sí, y luego deje que la próxima persona pase unos minutos descubriendo qué hace esta función. :)
Andrey Popov
1
La solución Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))no funciona si objes un objeto vacío. Cambiar a: Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v}))).
blackcatweb
117

No hay métodos nativos, pero lodash # mapValues hará el trabajo de manera brillante

_.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
// → { 'a': 3, 'b': 6, 'c': 9 }
Mario
fuente
66
No es necesario agregar una llamada HTTP adicional y una biblioteca adicional solo para una función. En cualquier caso, esta respuesta ahora está desactualizada y simplemente puede llamar Object.entries({a: 1, b: 2, c: 3})para obtener una matriz.
11
¿Por qué es útil obtener una matriz? El requisito era para un objeto mapeado. Tampoco hay una llamada HTTP implícita al usar Lodash, una biblioteca popular construida esencialmente para este tipo de cosas, aunque hay bastantes cosas que ES6 ha impedido la necesidad de usar bibliotecas de funciones de utilidad en algún grado últimamente.
corse32
2
Supongo que se requiere una llamada HTTP adicional para extraer a Lodash. Si este es su único caso de uso, de hecho es una exageración. Sin embargo, tiene sentido tener esta respuesta para Lodash es una biblioteca tan extendida.
igorsantos07
2
sin mencionar que en realidad debería usarlo _.map()para obtener la clave como segundo argumento, como lo requiere el OP.
igorsantos07
_.mapValues()también proporciona la clave como el segundo argumento. Tampoco _.map()funcionaría aquí, ya que solo devuelve matrices, no objetos:_.map({ 'a': 1, 'b': 2, 'c': 3}, n => n * 3) // [3, 6, 9]
MeltedPenguin
57

Es bastante fácil escribir uno:

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

con código de ejemplo:

> 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: esta versión también le permite (opcionalmente) establecer el thiscontexto para la devolución de llamada, al igual que el Arraymétodo.

EDITAR : modificado para eliminar el uso de Object.prototype, para garantizar que no entre en conflicto con ninguna propiedad existente nombrada mapen el objeto.

Alnitak
fuente
3
Okay. :) Un comentario sobre esta pregunta me trajo aquí. Haré +1 en esto ya que la respuesta aceptada es claramente un mal uso map, pero debo decir que la modificación Object.prototypeno me sienta bien, incluso si no es enumerable.
JLRishe
2
@JLRishe Gracias. En mi humilde opinión, en ES5 realmente ya no hay ninguna razón para no modificar Object.prototypemás que el riesgo (teórico) de colisión con otros métodos. FWIW, no puedo entender cómo la otra respuesta obtuvo tantos votos como tiene, está completamente mal.
Alnitak
77
@JLRishe eres un viejo geezer ... ¡ponte al día y modifica ese prototipo!
Nick Manning
12
@NickManning Ustedes jóvenes whippersnappers y su comportamiento irresponsable. ¡Sal de mi césped!
JLRishe
3
Esto ya no funciona: o = {map: true, image: false}. Lo arriesgas solo por escribir en o.map(fn)lugar demap(o,fn)
fregante
25

Puede usar Object.keysy luego forEachsobre la matriz de claves devuelta:

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

O de una manera más modular:

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

Tenga en cuenta que Object.keysdevuelve una matriz que contiene solo las propiedades enumerables propias del objeto, por lo que se comporta como un for..inbucle con una hasOwnPropertymarca de verificación.

Mattias Buelens
fuente
20

Esto es directo, y todos en la comunidad de JS lo saben. No debería ser esta funcionalidad:

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}

Aquí está la implementación ingenua:

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

es muy molesto tener que implementar esto usted mismo todo el tiempo;)

Si quieres algo un poco más sofisticado, que no interfiera con la clase Object, prueba esto:

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

pero es seguro agregar esta función de mapa a Object, simplemente no agregue a Object.prototype.

Object.map = ... // fairly safe
Object.prototype.map ... // not ok
Alexander Mills
fuente
¿Por qué agrega esta función al Objectobjeto global ? Solo tengo const mapObject = (obj, fn) => { [...]; return ret; }.
Amberlamps
77
Porque debería ser un método en Object :)
Alexander Mills
1
Mientras no nos metamos con Object.prototype, entonces deberíamos estar bien
Alexander Mills
1
Si el mapa fuera una cosa vaga al azar, claro. Pero .mapgeneralmente se entiende como una interfaz Functor, y no existe una definición única de Functor para "Objeto" porque prácticamente cualquier cosa en JavaScript puede ser un Objeto, incluidas las cosas con sus propias interfaces / lógica .map muy distintas y definidas.
Dtipson
1
En mi opinión, el primer argumento de devolución de llamada debe ser un elemento iterado en sí mismo, no una clave. Por ejemplo, supongo que debería darme el mismo objeto, como suele ocurrir con el mapa común: Object.map ({prop: 1}, el => el) pero devuelve claves con el valor del mismo nombre de clave ...
Gleb Dolzikov
17

Vine aquí buscando encontrar y responder para asignar un objeto a una matriz y obtuve esta página como resultado. En caso de que haya venido aquí buscando la misma respuesta que yo, así es como puede asignar y objetar a una matriz.

Puede usar map para devolver una nueva matriz del objeto de la siguiente manera:

var newObject = Object.keys(myObject).map(function(key) {
   return myObject[key];
});
JoeTron
fuente
66
Esto no funciona de forma remota: solo devuelve una matriz de valores del objeto, no un objeto nuevo. No puedo creer que tenga tantos votos positivos.
Alnitak
1
Estás en lo correcto, no responde la pregunta correctamente ya que estaban buscando una respuesta para mapear un objeto y un objeto. Vine aquí buscando una respuesta para asignar un objeto a una matriz y obtuve esta página como resultado, así que dejaré mi respuesta y la editaré para reflejar que es una respuesta alternativa para las personas que pueden haber llegado aquí como resultado de buscando mapear un objeto a matrices.
JoeTron
55
En ES7 eso será trivialmenteObject.values(myObject)
Alnitak
1
Es bueno saberlo, pero algunas personas todavía tienen que usar código heredado.
JoeTron
const obj = {foo: 'bar', baz: 42}; console.log (Object.entries (obj)); // [['' foo ',' bar '], [' baz ', 42]]
Bashirpour
12

La respuesta aceptada tiene dos inconvenientes:

  • Mal uso Array.prototype.reduce , porque reducir significa cambiar la estructura de un tipo compuesto, lo que no sucede en este caso.
  • No es particularmente reutilizable.

Un enfoque funcional ES6 / ES2015

Tenga en cuenta que todas las funciones se definen en forma de 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));

  • En la línea A ose reasigna. Dado que Javascript pasa los valores de referencia al compartir , ose genera una copia superficial de . Ahora podemos mutar odentro omapsin mutaro en el ámbito primario.
  • En la línea B mapse ignora el valor de retorno, porque maprealiza una mutación de o. Dado que este efecto secundario permanece dentro omapy no es visible en el ámbito primario, es totalmente aceptable.

Esta no es la solución más rápida, sino declarativa y reutilizable. Aquí está la misma implementación que una línea, sucinta pero menos legible:

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

Anexo: ¿por qué los objetos no son iterables por defecto?

ES2015 especificó el iterador y los protocolos iterables. Pero los objetos aún no son iterables y, por lo tanto, no se pueden mapear. La razón es la mezcla de datos y nivel de programa .

Comunidad
fuente
1
Parece un poco sobre diseñado; Cuento 6 funciones para lo que es esencialmente un bucle. Otras soluciones son mucho más simples y la respuesta aceptada es 5 veces más rápida también.
fregante
44
@ bfred.it ¿Cuál es el propósito de tu comentario? Si te gusta la micro-optimización, tampoco deberías usarla Array.prototype.reduce. Todo lo que quiero ilustrar aquí es que la respuesta aceptada de alguna manera "abusa" Array.prototype.reducey cómo se podría implementar un mapeo puramente funcional. Si no está interesado en la programación funcional, simplemente ignore mi respuesta.
2
"reducir significa cambiar la estructura de un tipo compuesto" No creo que sea cierto. Un Array.reduce que produce una nueva matriz de la misma longitud exacta o incluso los mismos valores exactos es perfectamente legítimo. Reducir puede usarse para recrear la funcionalidad del mapa y / o filtro (o ambos, como en un transductor complejo, para lo cual reducir es la base). En FP, es un tipo de pliegue, y un pliegue que termina con el mismo tipo sigue siendo un pliegue.
Dtipson
2
@Dtipson Tienes toda la razón. A foldes más genérico que un map(functor). Sin embargo, este fue mi conocimiento desde mediados de 2016. Eso es media eternidad: D
1
Ja, sip. Me ha llevado años darme cuenta de lo interconectado que está todo esto y el ecosistema más amplio de términos e interfaces. Las cosas que parecen ser tan fáciles resultan ser increíblemente complejas, y algunas cosas que parecen imposiblemente complejas se resuelven de manera bastante clara. :)
Dtipson
10

JavaScript acaba de recibir el nuevo Object.fromEntries método.

Ejemplo

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)

Explicación

El código anterior convierte el objeto en una matriz anidada ( [[<key>,<value>], ...]) que puede asignar.Object.fromEntriesconvierte la matriz de nuevo en un objeto.

Lo bueno de este patrón es que ahora puede tener fácilmente en cuenta las claves de los objetos durante el mapeo.

Documentación

Soporte de navegador

Object.fromEntriesactualmente solo es compatible con estos navegadores / motores , sin embargo, hay polyfills disponibles (por ejemplo, @ babel / polyfill ).

HaNdTriX
fuente
2
Básicamente, Object.fromEntrieses lo contrario de Object.entries. Object.entriesconvierte un objeto en una lista de pares clave-valor. Object.fromEntriesconvierte una lista de pares clave-valor en un objeto.
orad
8

Versión mínima (es6):

Object.entries(obj).reduce((a, [k, v]) => (a[k] = v * v, a), {})
Yukulélé
fuente
Creo que esto es incorrecto debido a un error tipográfico, creo que quieres decir => Object.entries(obj).reduce((a, [k, v]) => [a[k] = v * v, a], {}), corchetes angulados en lugar de paréntesis.
Alexander Mills
@AlexanderMills, mi código es correcto. Lea más sobre el operador de coma entre paréntesis: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Yukulélé
entendido, es posible que desee explicar eso la próxima vez, pero supongo que para eso están los comentarios.
Alexander Mills
1
básicamente, mi código es equivalente a:Object.entries(obj).reduce((a, [k, v]) => {a[k] = v * v; return a}, {})
Yukulélé
1
Gran respuesta. Object.entries().reducees la mejor solución creo que con ES6 + .WOULD obtener más upvotes si se utiliza un returncomunicado en el reductor en lugar de implicit returny commadel operador - que está ordenada, pero difícil de leer en mi humilde opinión
Drenai
7

Para el máximo rendimiento.

Si su objeto no cambia con frecuencia pero necesita iterarse con frecuencia, sugiero usar un mapa nativo como caché.

// 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 ya funciona en Chrome, Edge, Firefox y beta Opera, por lo que es una característica a prueba de futuro. Es de ES7, así que polyfill lo https://github.com/es-shims/Object.entries para IE donde no funciona.

Pawel
fuente
¿Qué tal usar Destructing ?
Константин Ван
1
@ K._ solía ser muy lento, pero ahora no sé sobre el rendimiento. Parece que V8 v 5.6 introdujo algunas optimizaciones para la desestructuración, pero eso debe medirse v8project.blogspot.co.uk
Pawel
O, menos eficiente es7 pero inmutable:const fn = v => v * 2; const newObj = Object.entries(myObject).reduce((acc, [k,v]) => Object.assign({}, acc, {[k]: fn(v)}), {});
mikebridge
Me pregunto por qué ninguna de las otras respuestas usa Object.entries. Es mucho más limpio que iterar sobre las teclas y usar obj [clave].
corwin.amber
6

puede usar el mapmétodo y forEachen matrices, pero si desea usarlo, Objectpuede usarlo con un giro como el siguiente:

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

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

O puede usar otros bucles también como $.eachmétodo como el siguiente ejemplo:

$.each(ob,function (key, value) {
  ob[key] *= value;
});
console.log(ob) //it will also log as {a: 4, b: 16, c: 36}
Haritsinh Gohil
fuente
¿Dónde $está jquery?
masterxilo
Sí, puedes usar $ y jQuery
Haritsinh Gohil el
1
@Ronald, he entendido mal, vea mi respuesta actualizada, gracias por no votar negativamente, pero realmente me he dado cuenta de mi error, gracias por mejorar la comunidad.
Haritsinh Gohil
@bcoughlan sí, es exactamente lo que he escrito en el comentario también, está cuadrando los números, ¿qué quieres entonces?
Haritsinh Gohil
1
@HaritsinhGohil Supuse que te estaba criticando por dar una solución jQuery. Es cierto que en estos días hay cierto ímpetu para alejarse de jQuery y, por supuesto, hay Node, pero a menos que un OP solicite específicamente JS o Node puro, creo que está bien asumir que alguien está trabajando en un entorno web y que jQuery es casi seguro que estará disponible.
abalter
4

El map functionno existe en el, Object.prototypesin embargo, puede emularlo así

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
fuente
Problemas de rendimiento: typeof callback === 'function'absolutamente redundante. 1) mapno tiene sentido sin devolución de llamada 2) si callbackno es una función, la mapimplementación suya solo se ejecuta sin sentido for-loop. Además, Object.prototype.hasOwnProperty.call( obj, key )es un poco exagerado.
ankhzet
3

Encontré esto como un primer elemento en una búsqueda en Google tratando de aprender a hacer esto, y pensé que compartiría para otras personas que encontraron esto recientemente la solución que encontré, que utiliza el paquete npm inmutable.

Creo que es interesante compartir porque inmutable utiliza la situación EXACTA del OP en su propia documentación: lo siguiente no es mi propio código, sino que se extrajo de la documentación actual de inmutable-js:

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 } 

No es que Seq tenga otras propiedades ("Seq describe una operación diferida, lo que les permite encadenar eficientemente el uso de todos los métodos de recopilación de orden superior (como el mapa y el filtro) al no crear colecciones intermedias") y que otros datos inmutables de js las estructuras también pueden hacer el trabajo de manera bastante eficiente.

Cualquiera que use este método, por supuesto, tendrá que npm install immutable que leer los documentos:

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

alex.h
fuente
3

EDITAR: la forma canónica de usar las nuevas funciones de JavaScript es:

const identity = x =>
  x

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

¿Dónde oestá algún objeto y fes su función de mapeo? O podríamos decir, dada una función de a -> b, y un objeto con valores de tipo a, producir un objeto con valores de tipo b. Como una firma de pseudo tipo -

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

La respuesta original fue escrita para demostrar un poderoso combinador, mapReduceque nos permite pensar en nuestra transformación de una manera diferente

  1. m, la función de mapeo - le da la oportunidad de transformar el elemento entrante antes ...
  2. r, la función reductora : esta función combina el acumulador con el resultado del elemento asignado

Intuitivamente, mapReducecrea un nuevo reductor que podemos conectar directamente Array.prototype.reduce. Pero, lo que es más importante, podemos implementar nuestra implementación de functor de objetos de manera simple omaputilizando el monoide de objeto Object.assigny {}.

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 }

Observe que la única parte del programa que realmente tuvimos que escribir es la implementación de mapeo en sí misma:

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

¿Qué dice, dado un objeto conocido oy alguna clave k, construir un objeto y cuya propiedad computada kes el resultado de la llamada fen el valor de la clave, o[k].

Echamos un vistazo al mapReducepotencial de secuenciación si primero hacemos un resumenoreduce

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

Todo funciona igual, pero ahora omapse puede definir en un nivel superior. Por supuesto, lo nuevo Object.entrieshace que esto parezca tonto, pero el ejercicio sigue siendo importante para el alumno.

No verá todo el potencial mapReduceaquí, pero comparto esta respuesta porque es interesante ver cuántos lugares se puede aplicar. Si está interesado en cómo se deriva y otras formas en que podría ser útil, consulte esta respuesta .

Gracias
fuente
44
Usemos más bien Mapy Map.entries, OK: D. Estoy cansado de abusar de objetos simples como tipos de datos de mapas.
2
Estoy de acuerdo en que Mapdebe usarse donde / cuando sea posible, pero no reemplaza totalmente la necesidad de usar estos procedimientos en objetos JS simples aunque: D
Gracias
2

Basado en la respuesta de @Amberlamps, aquí hay una función de utilidad (como comentario, se veía feo)

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

y el uso es:

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
fuente
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
fuente
enumerableel valor predeterminado esfalse
gracias el
2

Mi respuesta se basa en gran medida en la respuesta mejor calificada aquí y espero que todos entiendan (también tengo la misma explicación en mi GitHub). Es por eso que su impedancia con el mapa funciona:

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

El propósito de la función es tomar un objeto y modificar el contenido original del objeto utilizando un método disponible para todos los objetos (objetos y matrices por igual) sin devolver una matriz. Casi todo dentro de JS es un objeto, y por esa razón los elementos más abajo en la tubería de herencia pueden usar técnicamente aquellos disponibles para aquellos en la línea (y al revés parece).

La razón por la que esto funciona se debe a que las funciones .map devuelven una matriz que REQUIERE que proporcione un RETORNO explícito o implícito de una matriz en lugar de simplemente modificar un objeto existente. Básicamente, engañas al programa para que piense que el objeto es una matriz usando Object.keys, que te permitirá utilizar la función de mapa con su actuación sobre los valores con los que están asociadas las teclas individuales (en realidad, accidentalmente devolví matrices pero lo arreglé). Mientras no haya un retorno en el sentido normal, no se creará una matriz con el objeto original intacto y modificado según lo programado.

Este programa en particular toma un objeto llamado imágenes y toma los valores de sus claves y agrega etiquetas de URL para usar dentro de otra función. Original es esto:

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

... y modificado es esto:

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 estructura original del objeto se deja intacta, lo que permite el acceso normal a la propiedad siempre que no haya un retorno. NO haga que devuelva una matriz como normal y todo estará bien. El objetivo es REASIGNAR los valores originales (imágenes [clave]) a lo que se desea y no a nada más. Por lo que sé, para evitar la salida de la matriz, TIENE que haber REASIGNACIÓN de imágenes [clave] y no hay una solicitud implícita o explícita de devolver una matriz (la asignación de variables hace esto y me fallaba de un lado a otro).

EDITAR:

Para abordar su otro método con respecto a la creación de nuevos objetos para evitar modificar el objeto original (y parece que la reasignación sigue siendo necesaria para evitar crear accidentalmente una matriz como salida). Estas funciones usan sintaxis de flecha y son si simplemente desea crear un nuevo objeto para uso futuro.

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

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

La forma en que funcionan estas funciones es así:

mapFn toma la función que se agregará más tarde (en este caso (valor) => valor) y simplemente devuelve lo que esté almacenado allí como un valor para esa clave (o multiplicado por dos si cambia el valor de retorno como lo hizo) en mapFn ( obj) [clave],

y luego redefine el valor original asociado con la clave en el resultado [clave] = mapFn (obj) [clave]

y devuelve la operación realizada en el resultado (el acumulador ubicado en los corchetes iniciados al final de la función .reduce).

Todo esto se está realizando en el objeto elegido y TODAVÍA NO PUEDE haber una solicitud implícita de una matriz devuelta y solo funciona al reasignar valores, por lo que puedo decir. Esto requiere algo de gimnasia mental pero reduce las líneas de código necesarias como se puede ver arriba. La salida es exactamente la misma que se puede ver a continuación:

{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"}

Tenga en cuenta que esto funcionó con NON-NUMBERS. PUEDE duplicar CUALQUIER objeto SIMPLEMENTE DEVOLVIENDO EL VALOR en la función mapFN.

Andrew Ramshaw
fuente
2

La primera función responde a la pregunta. Crea un mapa.

La segunda función no crea un mapa. En su lugar, recorre las propiedades del objeto existente usando hasOwnProperty(). Esta alternativa de mapa puede ser una mejor solución en algunas situaciones.

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
fuente
2
Esto no responde a la pregunta original: no está asignando los valores a un nuevo objeto con una función proporcionada (es decir, no se parece en nada a Array.prototype.map).
Matt Browne
@MattBrowne Agregué un segundo método que crea lo que podría decirse que es un mapa. Es lento, pero hace lo que el OP solicita. El primer método es ideal en muchas situaciones, por eso utilicé un bucle y funciones de propiedad.
Victor Stoddard
1

Si está interesado en hacer mapping no solo a los valores sino también a las claves, he escrito Object.map(valueMapper, keyMapper)que se comporta de esta manera:

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
fuente
3
Supongo que básicamente has proporcionado un enlace como respuesta, lo cual está mal visto.
Chris Wright
Si alguna vez útil para cualquier persona: npm install @mattisg/object.map.
MattiSG
1

También necesitaba una versión que permitiera modificar las claves (basada en las respuestas de @Amberlamps y @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"}
}

Editar: ligero cambio para pasar en el objeto inicial {}. Permite que sea [] (si las teclas son enteras)

Simétrico
fuente
1

Específicamente, quería usar la misma función que estaba usando para las matrices de un solo objeto, y quería que fuera simple. Esto funcionó para mí:

var mapped = [item].map(myMapFunction).pop();
Jason Norwood-Young
fuente
esto no es diferente avar mapped = myMapFunction(item)
Gracias
Esto es mucho más simple que todas las otras soluciones de "ciencia espacial" que vi aquí. Gracias por compartir :)
Yuri Teixeira
1

Para responder más de cerca a lo que precisamente solicitó el OP, el OP quiere un objeto:

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

tener un método de mapa myObject.map,

similar a Array.prototype.map que se usaría de la siguiente manera:

newObject = myObject.map (function (value, label) {
    valor de retorno * valor;
});
// newObject ahora es {'a': 1, 'b': 4, 'c': 9}

La mejor respuesta de imho (medida en términos de " cerca de lo que se pregunta " + "no se necesita ES {5,6,7} innecesariamente") sería:

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

El código anterior evita el uso intencional de cualquier función de lenguaje, solo disponible en ediciones recientes de ECMAScript. Con el código anterior, el problema se puede resolver como esto:

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);
código de prueba alternativo aquí

Además de ser mal visto por algunos, sería una posibilidad insertar la solución en la cadena de prototipos como esta.

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

Algo que, cuando se realiza con supervisión cuidadosa, no debería tener ningún efecto negativo y no afectar el mapmétodo de otros objetos (es decir, el de Array map).

humanidad y paz
fuente
1

Definir una función mapEntries.

mapEntries toma una función de devolución de llamada que se llama en cada entrada del objeto con el valor de los parámetros, la clave y el objeto. Debería devolver un nuevo valor.

mapEntries debería devolver un nuevo objeto con los nuevos valores devueltos por la devolución de llamada.

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}

Editar: una versión anterior no especificaba que esta no es una propiedad enumerable

Arye Eidelman
fuente
0

Hey escribió una pequeña función de mapeador que podría ayudar.

    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
fuente
1
Supongo que querías decir "yo" en lugar de "Hey"
Carlo
0

Una versión diferente de esto es usar una función personalizada json stringify que también puede funcionar en objetos profundos. Esto podría ser útil si tiene la intención de publicarlo en el servidor de todos modos como 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))

Sin fin
fuente
0

Solo manejo cadenas para reducir las exenciones:

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

Mapeador de objetos en TypeScript

Me gustan los ejemplos que usan Object.fromEntriescomo este , pero aún así, no son muy fáciles de usar. Las respuestas que usan Object.keysy luego buscan keyestán haciendo múltiples búsquedas que pueden no ser necesarias.

Desearía que hubiera una Object.mapfunción, pero podemos crear la nuestra y llamarla objectMapcon la capacidad de modificar ambas keyy value:

Uso (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' }

Código (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 no está utilizando TypeScript, copie el código anterior en TypeScript Playground para obtener el código JavaScript.

Además, la razón que pongo keySelectordespués valSelectoren la lista de parámetros es porque es opcional.

* Algunos créditos van a la respuesta de alexander-mills .

orad
fuente
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}]}]}

Esta función recorre recursivamente el objeto y las matrices de objetos. Los atributos se pueden eliminar si se devuelven indefinidos

Stanislav
fuente