função de mapa para objetos (em vez de matrizes)

1066

Eu tenho um objeto:

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

Estou procurando um método nativo, semelhante ao Array.prototype.mapque seria usado da seguinte maneira:

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

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

O JavaScript tem essa mapfunção para objetos? (Quero isso para o Node.JS, para não me importar com problemas entre navegadores.)

Randomblue
fonte
1
A maioria das respostas usa Object.keys, que não tem nenhuma ordem bem definida. Isso pode ser problemático, sugiro usar Object.getOwnPropertyNames.
Oriol
14
Surpreendente para mim, JS não forneceu essa tarefa incrivelmente rudimentar.
jchook
3
@Oriol você tem certeza disso? De acordo com os documentos da web do MDN, a ordem dos elementos da matriz é consistente entre Object.keyse Object.getOwnPropertyNames. Veja developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Bart
@ Bart A ordem de Object.keysdepende da implementação. Veja stackoverflow.com/a/30919039/1529630
Oriol
4
@ Oriol Você não deveria confiar na ordem das chaves nos objetos, então é meio irrelevante para a pergunta - já que ele nunca especificou que a ordem era importante para ele. E se a ordem importava para ele, ele não deveria estar usando nenhum objeto de qualquer maneira.
BT

Respostas:

1544

Não há nativo mappara o Objectobjeto, mas e quanto a isso:

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 }

Mas você pode facilmente iterar sobre um 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 }

Atualizar

Muitas pessoas estão mencionando que os métodos anteriores não retornam um novo objeto, mas operam no próprio objeto. Para esse assunto, eu queria adicionar outra solução que retorne um novo objeto e deixe o objeto original como ele é:

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.reducereduz uma matriz para um único valor, mesclando um pouco o valor anterior com o atual. A cadeia é inicializada por um objeto vazio {}. Em cada iteração, uma nova chave myObjecté adicionada com o dobro da chave como valor.

Atualizar

Com os novos recursos do ES6, há uma maneira mais elegante de se expressar 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
fonte
3
Sim, eu não recomendaria minha solução para esse tipo de problema. Por isso, atualizei minha resposta com uma solução alternativa e melhor.
Amberlamps
1
Atualizei minha resposta - parece (ao reler a pergunta) que o OP deseja um novo objeto com as mesmas chaves, onde os valores foram alterados com a função fornecida, e não modificar o objeto original.
Alnitak
13
@KhalilRavanna Acho que você leu errado o código aqui - esta resposta não está sendo usada mapcorretamente porque não está fazendo um return- está abusando mapcomo se fosse uma forEachchamada. Se ele realmente o fizesse return myObject[value] * 2 , o resultado seria uma matriz contendo os valores originais duplicados, em vez de um objeto contendo as chaves originais com valores duplicados, sendo o último claramente o que o OP solicitava.
Alnitak
3
@Amberlamps, seu .reduceexemplo é bom, mas IMHO ainda está muito longe da conveniência de um .mappara objetos, já que seu .reduceretorno de chamada não apenas precisa corresponder à maneira que .reducefunciona, mas também exige que myObjectesteja disponível no escopo lexical. O último, em particular, torna impossível passar apenas uma referência de função no retorno de chamada, exigindo uma função anônima no local.
Alnitak
8
TBH, eu preferiria uma resposta que apenas (ou originalmente) forneceu a segunda solução apresentada nesta resposta. O primeiro funciona e não está necessariamente errado, mas, como outros já declararam, não gosto de ver o mapa sendo usado dessa maneira. Quando eu ver mapa, minha mente é automaticamente pensar "estrutura de dados imutáveis"
mjohnsonengr
308

Que tal um liner com atribuição imediata de variável em JS simples ( ES6 / ES2015 )?

Utilizando o operador spread e a sintaxe do nome da chave computada :

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

jsbin

Outra versão usando reduzir:

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

jsbin

Primeiro exemplo como uma função:

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

Se você deseja mapear um objeto aninhado recursivamente em um estilo funcional , isso pode ser feito da seguinte maneira:

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 mais imperativamente, assim:

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 o ES7 / ES2016, você pode usar em Object.entries()vez de, Object.keys()por exemplo, o seguinte:

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

Introduzido o ES2019Object.fromEntries() , que simplifica ainda mais isso:

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


Propriedades herdadas e a cadeia de protótipos:

Em uma situação rara, pode ser necessário mapear um objeto de classe que contém propriedades de um objeto herdado em sua cadeia de protótipos . Nesses casos Object.keys(), não funcionará, porque Object.keys()não enumera propriedades herdadas . Se você precisar mapear propriedades herdadas , use for (key in myObj) {...}.

Aqui está um exemplo de um objeto que herda as propriedades de outro objeto e como Object.keys()não funciona nesse cenário.

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

No entanto, faça-me um favor e evite a herança . :-)

Rotareti
fonte
1
Bonito, mas fui pego pelo fato de Object.keysnão enumerar propriedades herdadas. Eu sugiro que você adicione um aviso.
David Braun
Para encurtar sua resposta, basta usar Object.entries({a: 1, b: 2, c: 3}).
1
Você tem alguns erros de digitação (você se refere (o, f)como argumentos, mas usa objno corpo.
kzahel 11/11
4
Sim, e então deixe a próxima pessoa dedicar alguns minutos para descobrir o que essa função faz? :)
Andrey Popov
1
A solução Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * v})))não funciona se objfor um objeto vazio. Mudar para: Object.assign({}, ...Object.entries(obj).map(([k, v]) => ({[k]: v * v}))).
21819
117

Nenhum método nativo, mas o lodash # mapValues fará o trabalho de maneira brilhante

_.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
// → { 'a': 3, 'b': 6, 'c': 9 }
Mario
fonte
6
Não há necessidade de adicionar uma chamada HTTP extra e uma biblioteca extra apenas para uma função. De qualquer forma, esta resposta está desatualizada e você pode simplesmente ligar Object.entries({a: 1, b: 2, c: 3})para obter uma matriz.
11
Por que a obtenção de uma matriz é útil? O requisito era para um objeto mapeado. Também não há chamada HTTP implícita no uso do Lodash, uma biblioteca popular criada essencialmente para esse tipo de coisa, embora existam algumas coisas em que o ES6 tenha impedido a necessidade de usar as bibliotecas de funções utilitárias em algum grau recentemente.
precisa saber é o seguinte
2
chamada HTTP extra para puxar Lodash, eu acho. Se esse é seu único caso de uso, é um exagero. No entanto, faz sentido ter essa resposta, pois a Lodash é uma biblioteca tão difundida.
igorsantos07
2
para não mencionar que você deve realmente usar _.map()para obter a chave como o segundo argumento - conforme exigido pelo OP.
igorsantos07
_.mapValues()também fornece a chave como o segundo argumento. Também _.map()não funcionaria aqui, pois apenas retorna matrizes, não objetos:_.map({ 'a': 1, 'b': 2, 'c': 3}, n => n * 3) // [3, 6, 9]
MeltedPenguin
57

É muito fácil escrever um:

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

com código de exemplo:

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

Nota: esta versão também permite definir (opcionalmente) o thiscontexto do retorno de chamada, assim como o Arraymétodo.

EDIT - alterado para remover o uso de Object.prototype, para garantir que não colidir com nenhuma propriedade existente nomeada mapno objeto.

Alnitak
fonte
3
Está bem. :) Um comentário sobre esta questão me trouxe aqui. Marcarei +1 com isso, já que a resposta aceita é claramente incorreta map, mas devo dizer que a modificação Object.prototypenão se encaixa bem comigo, mesmo que não seja enumerável.
JLRishe
2
@JLRishe Thanks. IMHO no ES5 realmente não há mais motivo para não modificar Object.prototypeo risco (teórico) de colisão com outros métodos. FWIW, eu não consigo entender como a outra resposta conseguiu tantos votos quanto ela tem, está completamente errado.
Alnitak
7
@JLRishe você é um velhote velho ... fique com os tempos e modifique esse protótipo!
Nick Manning
12
@NickManning Vocês jovens gargalhões e seu comportamento irresponsável. Saia do meu gramado!
JLRishe
3
Esta não funciona: o = {map: true, image: false}. Você está arriscando apenas para escrever o.map(fn), em vez demap(o,fn)
fregante
25

Você pode usar Object.keyse, em seguida, forEachsobre a matriz de chaves retornada:

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 uma forma mais 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; });

Observe que Object.keysretorna uma matriz que contém apenas as propriedades enumeráveis ​​do objeto, portanto, ele se comporta como um for..inloop com uma hasOwnPropertyverificação.

Mattias Buelens
fonte
20

Isso é bs direto, e todos na comunidade JS sabem disso. Não deve ser essa funcionalidade:

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}

aqui está a implementação ingênua:

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

é super chato ter que implementar isso você mesmo o tempo todo;)

Se você deseja algo um pouco mais sofisticado, que não interfira na classe Object, tente o seguinte:

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

mas é seguro adicionar essa função de mapa a Object, apenas não adicione a Object.prototype.

Object.map = ... // fairly safe
Object.prototype.map ... // not ok
Alexander Mills
fonte
Por que você adiciona essa função ao Objectobjeto global ? Apenas tenha const mapObject = (obj, fn) => { [...]; return ret; }.
Amberlamps
7
Porque deve ser um método em um objeto :)
Alexander Mills
1
Enquanto não mexermos com o Object.prototype, devemos ficar bem #
Alexander Mills
1
Se o mapa fosse apenas uma coisa vaga aleatória, com certeza. Mas .mapgeralmente é entendida como uma interface do Functor, e não existe uma definição única do Functor para "Objeto" porque praticamente qualquer coisa em javascript pode ser um Objeto, incluindo itens com interfaces / lógica .map muito distintas e definidas.
Dtipson
1
Na minha opinião, o primeiro argumento do retorno de chamada deve ser o elemento iterado, não uma chave. Por exemplo, suponho que ele deva me dar o mesmo objeto, como costuma acontecer com o mapa comum: Object.map ({prop: 1}, el => el), mas retorna chaves com valor com o mesmo nome de chave ...
Gleb Dolzikov
17

Eu vim aqui procurando encontrar e responder para mapear um objeto para uma matriz e obtive esta página como resultado. Caso você tenha vindo aqui procurando a mesma resposta que eu, aqui está como você pode mapear e objetar a uma matriz.

Você pode usar o mapa para retornar uma nova matriz do objeto da seguinte maneira:

var newObject = Object.keys(myObject).map(function(key) {
   return myObject[key];
});
JoeTron
fonte
6
Isso não funciona remotamente - apenas retorna uma matriz dos valores do objeto, não um novo objeto. Não acredito que ele tenha tantos votos positivos.
Alnitak
1
Você está correto, ele não responde a pergunta corretamente, pois eles estavam procurando uma resposta para mapear um objeto e um objeto. Eu vim aqui procurando uma resposta para mapear um objeto para uma matriz e obtive esta página como resultado; portanto, deixarei minha resposta e a editarei para refletir que é uma resposta alternativa para as pessoas que podem ter chegado aqui como resultado de procurando mapear um objeto para matrizes.
JoeTron
5
No ES7 isso será trivialmenteObject.values(myObject)
Alnitak
1
É bom saber, mas algumas pessoas ainda precisam usar código legado.
JoeTron
const obj = {foo: 'bar', baz: 42}; console.log (Object.entries (obj)); // [['foo', 'bar'], ['baz', 42]]
Bashirpour
12

A resposta aceita tem duas desvantagens:

  • Isso faz mau uso Array.prototype.reduce, porque reduzir significa alterar a estrutura de um tipo composto, o que não acontece neste caso.
  • Não é particularmente reutilizável

Uma abordagem funcional ES6 / ES2015

Observe que todas as funções são definidas na forma de caril.

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

  • Na linha A oé reatribuída. Como o Javascript passa valores de referência compartilhando , uma cópia superficial oé gerada. Agora, somos capazes de fazer uma mutação ointerna omapsem alterar oo escopo pai.
  • Na linha B map, o valor de retorno é ignorado, porque mapexecuta uma mutação de o. Como esse efeito colateral permanece dentro omape não é visível no escopo pai, é totalmente aceitável.

Esta não é a solução mais rápida, mas declarativa e reutilizável. Aqui está a mesma implementação que uma linha, sucinta mas menos legível:

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

Adendo - por que os objetos não são iteráveis ​​por padrão?

O ES2015 especificou o iterador e os protocolos iteráveis. Mas os objetos ainda não são iteráveis ​​e, portanto, não podem ser mapeados. O motivo é a mistura de dados e nível do programa .

Comunidade
fonte
1
Parece um pouco mais de engenharia; Eu conto 6 funções para o que é essencialmente um loop. Outras soluções são muito mais simples e a resposta aceita também é 5 vezes mais rápida.
Fregante
4
@ bfred.it Qual é o objetivo do seu comentário? Se você gosta de micro-otimização, também não deve usar Array.prototype.reduce. Tudo o que quero ilustrar aqui é que a resposta aceita de alguma forma "abusa" Array.prototype.reducee como um mapeamento puramente funcional pode ser implementado. Se você não está interessado em programação funcional, ignore minha resposta.
2
"reduzir significa mudar a estrutura de um tipo composto" Eu não acho que isso seja verdade. Um Array.reduce que produz uma nova matriz do mesmo tamanho exato ou mesmo dos mesmos valores é perfeitamente legítimo. Reduzir pode ser usado para recriar a funcionalidade do mapa e / ou filtro (ou ambos, como em um transdutor complexo, para o qual reduzir é a base). No FP, é um tipo de dobra, e uma dobra que termina com o mesmo tipo ainda é uma dobra.
Dtipson
2
@ Dtipson Você está absolutamente certo. A foldé mais genérico que um map(functor). No entanto, esse foi meu conhecimento desde meados de 2016. Isso é meia eternidade: D
1
Ah sim. Levei anos para perceber como essas coisas estão interconectadas e o ecossistema mais amplo de termos e interfaces. Coisas que parecem tão fáceis acabam se tornando impossivelmente complexas, e algumas coisas que parecem impossivelmente complexas abstraem bastante ordenadamente. :)
Dtipson
10

O JavaScript acabou de receber o novo Object.fromEntriesmétodo.

Exemplo

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)

Explicação

O código acima converte o objeto em uma matriz aninhada ( [[<key>,<value>], ...]) na qual você pode mapear. Object.fromEntriesconverte a matriz de volta em um objeto.

O legal desse padrão é que agora você pode facilmente levar em consideração as chaves do objeto durante o mapeamento.

Documentação

Suporte do navegador

Object.fromEntriesatualmente é suportado apenas por esses navegadores / mecanismos , no entanto, existem polyfills disponíveis (por exemplo, @ babel / polyfill ).

HaNdTriX
fonte
2
Basicamente, Object.fromEntriesé o oposto de Object.entries. Object.entriesconverte um objeto em uma lista de pares de valores-chave. Object.fromEntriesconverte uma lista de pares de valores-chave em um objeto.
orad 17/07/19
8

Versão mínima (es6):

Object.entries(obj).reduce((a, [k, v]) => (a[k] = v * v, a), {})
Yukulélé
fonte
Acho que isso está incorreto por causa de um erro de digitação, acho que você quer dizer => Object.entries(obj).reduce((a, [k, v]) => [a[k] = v * v, a], {}), colchetes angulares em vez de parênteses.
Alexander Mills
@AlexanderMills, meu código está correto. Leia mais sobre o operador de vírgula entre parênteses: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Yukulélé
entendi, você pode explicar isso da próxima vez, mas acho que é para isso que servem os comentários.
Alexander Mills
1
basicamente, meu código é equivalente a:Object.entries(obj).reduce((a, [k, v]) => {a[k] = v * v; return a}, {})
Yukulélé 30/01
1
Ótima resposta. Object.entries().reduceé a melhor solução que eu acho que com ES6 + .Pretende obter mais upvotes se ele usou uma returndeclaração no redutor em vez de implicit returne commaoperador - que é limpo, mas difícil de ler IMHO
Drenai
7

Para desempenho máximo.

Se o seu objeto não muda frequentemente, mas precisa ser repetido com frequência, sugiro usar um mapa nativo como 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 já funciona no Chrome, Edge, Firefox e beta Opera, por isso é um recurso à prova de futuro. É do ES7, então preencha o https://github.com/es-shims/Object.entries para o IE, onde não funciona.

Pawel
fonte
Que tal usar a Destruição ?
Jun
1
@ K._ costumava ser muito lento, mas não sei sobre desempenho agora. Parece que a V8 v 5.6 introduziu algumas otimizações para a desestruturação, mas isso deve ser medido v8project.blogspot.co.uk
Pawel
Ou, menos desempenho es7, mas imutável: const fn = v => v * 2; const newObj = Object.entries(myObject).reduce((acc, [k,v]) => Object.assign({}, acc, {[k]: fn(v)}), {});
mikebridge
Eu estou querendo saber por que nenhuma das outras respostas usa Object.entries. É muito mais limpo do que iterar sobre as chaves e usar obj [key].
Corwin.amber 11/05/19
6

você pode usar o mapmétodo e as forEachmatrizes, mas se quiser usá- Objectlo, use-o com o twist abaixo:

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}

Ou você pode usar outros loops também como $.each método, como no exemplo abaixo:

$.each(ob,function (key, value) {
  ob[key] *= value;
});
console.log(ob) //it will also log as {a: 4, b: 16, c: 36}
Haritsinh Gohil
fonte
Onde $está o jquery?
Masterxilo # 12/18
Sim, você pode usar $ e jQuery
Haritsinh Gohil
1
@Ronald eu entendi errado, veja minha resposta atualizada, obrigado por não ter votado mal, mas realmente se conscientizado do meu erro, obrigado por melhorar a comunidade.
Haritsinh Gohil
@ bcoughlan sim, é exatamente o que eu escrevi no comentário também, é quadratura dos números, o que você quer então?
Haritsinh Gohil
1
@HaritsinhGohil Entendi que ele estava criticando você por fornecer uma solução jQuery. É verdade que atualmente há algum ímpeto para se afastar do jQuery e, claro, existe o Node, mas, a menos que um OP solicite especificamente JS ou Node puro, acho que é bom supor que alguém esteja trabalhando em um ambiente da Web e que o jQuery é quase certo que esteja disponível.
abalter 17/09/19
4

O map functionnão existe no Object.prototypeentanto, você pode emular assim

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
fonte
Problemas de desempenho: typeof callback === 'function'absolutamente redundante. 1) mapnão tem significado sem retorno de chamada 2) se callbacknão estiver funcionando, sua mapimplementação executa apenas um loop for sem sentido. Além disso, Object.prototype.hasOwnProperty.call( obj, key )é um pouco exagerado.
Ankhzet
3

Encontrei isso como um primeiro item em uma pesquisa no Google tentando aprender a fazer isso, e pensei em compartilhar com outros foliões que encontrassem isso recentemente a solução que encontrei, que usa o pacote npm imutável.

Acho interessante compartilhar porque o imutável usa a situação EXATA do OP em sua própria documentação - o seguinte não é meu próprio código, mas extraído da documentação atual do immutable-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 } 

Não que o Seq tenha outras propriedades ("O Seq descreve uma operação lenta, permitindo que eles encadeiem com eficiência o uso de todos os métodos de coleta de ordem superior (como mapa e filtro), não criando coleções intermediárias") e que alguns outros dados immutable-js estruturas também podem fazer o trabalho com bastante eficiência.

Qualquer pessoa que use esse método precisará, é claro, npm install immutablee poderá querer ler os documentos:

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

alex.h
fonte
3

EDIT: A maneira canônica de usar os recursos mais recentes do JavaScript é:

const identity = x =>
  x

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

Onde oestá algum objeto e fé sua função de mapeamento. Ou podemos dizer que, dada uma função de a -> be um objeto com valores de tipo a, produza um objeto com valores de tipo b. Como uma assinatura pseudo-tipo -

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

A resposta original foi escrita para demonstrar um poderoso combinador, mapReduceque nos permite pensar em nossa transformação de uma maneira diferente

  1. m, a função de mapeamento - permite transformar o elemento recebido antes de…
  2. r, a função redutora - essa função combina o acumulador com o resultado do elemento mapeado

Intuitivamente, mapReducecria um novo redutor ao qual podemos conectar diretamente Array.prototype.reduce. Porém, mais importante, podemos implementar nossa implementação de functor de objeto omapclaramente, utilizando o objeto monóide, Object.assigne {}.

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 a única parte do programa que realmente tivemos que escrever é a própria implementação de mapeamento -

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

O que diz que, dado um objeto conhecido oe alguma chave k, construa um objeto e cuja propriedade calculada kseja o resultado da chamada fdo valor da chave,o[k] ,.

Temos um vislumbre do mapReducepotencial de seqüenciamento se abstraímosoreduce

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

Tudo funciona da mesma forma, mas omappode ser definido em um nível superior agora. Claro que o novoObject.entries faz com que isso pareça bobo, mas o exercício ainda é importante para o aluno.

Você não verá todo o potencial mapReducedaqui, mas eu compartilho esta resposta, porque é interessante ver quantos lugares ela pode ser aplicada. Se você estiver interessado em saber como é derivado e de que outras maneiras poderia ser útil, consulte esta resposta .

Obrigado
fonte
4
Vamos usar Mape Map.entries, OK: D. Estou cansado de abusar de objetos simples como tipos de dados do mapa.
2
Concordo que Mapdeve ser usado onde / quando possível, mas não substitui totalmente a necessidade de usar estes procedimentos em objetos JS simples, embora tho: D
Obrigado
2

Com base na resposta @Amberlamps, aqui está uma função de utilitário (como um comentário, parecia feio)

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

e o uso é:

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
fonte
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
fonte
enumerableo padrão éfalse
Obrigado
2

Minha resposta é amplamente baseada na resposta mais alta classificada aqui e espero que todos entendam (tenha a mesma explicação no meu GitHub também). É por isso que sua impementação com o mapa funciona:

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

O objetivo da função é pegar um objeto e modificar o conteúdo original do objeto usando um método disponível para todos os objetos (objetos e matrizes) sem retornar uma matriz. Quase tudo no JS é um objeto e, por esse motivo, os elementos mais abaixo no pipeline de herança podem potencialmente usar tecnicamente aqueles disponíveis para os que estão na linha de frente (e o inverso aparece).

O motivo disso funcionar é que as funções .map retornam uma matriz, EXIGINDO que você forneça um RETURN explícito ou implícito de uma matriz, em vez de simplesmente modificar um objeto existente. Você basicamente engana o programa para pensar que o objeto é uma matriz usando Object.keys, que permitirá que você use a função map com sua atuação nos valores aos quais as chaves individuais estão associadas (na verdade, retornei acidentalmente matrizes, mas a corrigi). Enquanto não houver retorno no sentido normal, não haverá matriz criada com o objeto original ainda intacto e modificado conforme programado.

Esse programa específico pega um objeto chamado images e pega os valores de suas chaves e anexa tags de URL para uso em outra função. Original é este:

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

... e modificado é este:

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

A estrutura original do objeto é deixada intacta, permitindo o acesso normal à propriedade, desde que não haja retorno. NÃO faça com que ele retorne uma matriz como normal e tudo ficará bem. O objetivo é reatribuir os valores originais (imagens [chave]) ao que se deseja e não a mais nada. Até onde eu sei, para impedir a saída da matriz, TEM DE SER REASSIGNAMENTO de imagens [chave] e nenhuma solicitação implícita ou explícita para retornar uma matriz (a atribuição de variáveis ​​faz isso e estava sendo repetida para mim).

EDITAR:

Indo abordar seu outro método em relação à criação de novos objetos para evitar modificar o objeto original (e a reatribuição parece ainda ser necessária para evitar a criação acidental de uma matriz como saída). Essas funções usam sintaxe de seta e existem se você simplesmente deseja criar um novo 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);

A maneira como essas funções funcionam é assim:

mapFn pega a função a ser adicionada mais tarde (neste caso (valor) => valor) e simplesmente retorna o que estiver armazenado como um valor para essa chave (ou multiplicado por dois se você alterar o valor de retorno como ele fez) em mapFn ( obj) [chave],

e redefine o valor original associado à chave no resultado [chave] = mapFn (obj) [chave]

e retorna a operação executada no resultado (o acumulador localizado nos colchetes iniciado no final da função .reduce).

Tudo isso está sendo realizado no objeto escolhido e AINDA NÃO PODE haver uma solicitação implícita para uma matriz retornada e só funciona ao reatribuir valores, tanto quanto eu sei. Isso requer alguma ginástica mental, mas reduz as linhas de código necessárias, como pode ser visto acima. A saída é exatamente a mesma que pode ser vista abaixo:

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

Lembre-se de que isso funcionou com NÃO-NÚMEROS. Você pode duplicar QUALQUER objeto simplesmente retornando o valor na função mapFN.

Andrew Ramshaw
fonte
2

A primeira função responde à pergunta. Cria um mapa.

A segunda função não cria um mapa. Em vez disso, ele percorre as propriedades no objeto existente usando hasOwnProperty(). Essa alternativa de mapa pode ser uma solução melhor em algumas situações.

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
fonte
2
Isso não responde à pergunta original - não está mapeando os valores para um novo objeto com uma função fornecida (ou seja, isso não é como Array.prototype.map).
Matt Browne
@MattBrowne Adicionei um segundo método que cria o que é indiscutivelmente um mapa. É lento, mas faz o que o OP solicita. O primeiro método é ideal em muitas situações, razão pela qual usei um loop e funções de propriedade.
Victor Stoddard
1

Se você está interessado em mapexecutar ping, não apenas valores, mas também chaves, escrevi Object.map(valueMapper, keyMapper), que se comportam desta maneira:

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
fonte
3
Presumo que você basicamente forneceu um link como resposta, o que é desaprovado.
Chris Wright
Se alguma vez útil para qualquer um: npm install @mattisg/object.map.
27416 MattiSG #
1

Eu precisava de uma versão que permitisse modificar as chaves também (com base nas respostas @Amberlamps e @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: leve alteração para passar no objeto inicial {}. Permite que seja [] (se as chaves forem números inteiros)

Simétrico
fonte
1

Eu queria especificamente usar a mesma função que estava usando para matrizes para um único objeto e queria mantê-lo simples. Isso funcionou para mim:

var mapped = [item].map(myMapFunction).pop();
Jason Norwood-Young
fonte
isso não é diferente devar mapped = myMapFunction(item)
obrigado
Isso é muito simples do que todas as outras soluções de "foguete" que vi aqui. Obrigado por compartilhar :)
Yuri Teixeira
1

Para responder mais de perto ao que precisamente o OP solicitou, o OP deseja um objeto:

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

ter um método de mapa myObject.map,

semelhante ao Array.prototype.map que seria usado da seguinte maneira:

newObject = myObject.map (função (valor, rótulo) {
    valor de retorno * valor;
});
// newObject agora é {'a': 1, 'b': 4, 'c': 9}

A melhor resposta imho (medida em termos de " próximo ao que é solicitado " + "sem ES {5,6,7} necessário desnecessariamente") seria:

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

O código acima evita o uso intencional de qualquer recurso de idioma, disponível apenas nas edições recentes do ECMAScript. Com o código acima, o problema pode ser resolvido como:

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 teste alternativo aqui

Além de desaprovado por alguns, seria uma possibilidade de inserir a solução na cadeia de protótipos dessa maneira.

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, quando feito com uma supervisão cuidadosa, não deve ter efeitos negativos e não afetar o mapmétodo de outros objetos (por exemplo, os de Array map).

humanidade e paz
fonte
1

Defina uma função mapEntries.

mapEntries usa uma função de retorno de chamada que é chamada em cada entrada no objeto com o valor dos parâmetros, chave e objeto. Ele deve retornar um novo valor.

mapEntries deve retornar um novo objeto com os novos valores retornados do retorno de chamada.

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: uma versão anterior não especificou que esta não é uma propriedade enumerável

Arye Eidelman
fonte
0

Hey escreveu uma pequena função de mapeador que pode ajudar.

    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
fonte
1
Eu acho que você quis dizer "I" em vez de "Hey"
Carlo
0

Uma abordagem diferente é usar uma função customizada json stringify que também pode funcionar em objetos profundos. Isso pode ser útil se você pretende publicá-lo no servidor de qualquer maneira, 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))

Sem fim
fonte
0

Eu manuseio apenas cadeias de caracteres para reduzir isenções:

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

Mapeador de objetos em TypeScript

Eu gosto dos exemplos que usam Object.fromEntriescomo este , mas ainda assim, eles não são muito fáceis de usar. As respostas que usam Object.keyse pesquisam na keyverdade estão fazendo várias pesquisas que podem não ser necessárias.

Eu gostaria que houvesse uma Object.mapfunção, mas podemos criar a nossa e chamá-la objectMapcom a capacidade de modificar ambas keye 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;
}

Se você não estiver usando o TypeScript, copie o código acima no TypeScript Playground para obter o código JavaScript.

Além disso, o motivo que eu coloquei keySelectordepois valSelectorna lista de parâmetros é porque é opcional.

* Alguns créditos vão para a resposta de alexander-mills .

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

Essa função percorre recursivamente o objeto e as matrizes de objetos. Os atributos podem ser excluídos se retornados indefinidos

Stanislav
fonte