Como filtrar a matriz de objetos com base em atributos?

473

Eu tenho a seguinte matriz JavaScript de objetos domésticos de imóveis:

var json = {
    'homes': [{
            "home_id": "1",
            "price": "925",
            "sqft": "1100",
            "num_of_beds": "2",
            "num_of_baths": "2.0",
        }, {
            "home_id": "2",
            "price": "1425",
            "sqft": "1900",
            "num_of_beds": "4",
            "num_of_baths": "2.5",
        },
        // ... (more homes) ...     
    ]
}

var xmlhttp = eval('(' + json + ')');
homes = xmlhttp.homes;

O que eu gostaria de fazer é poder executar um filtro no objeto para retornar um subconjunto de objetos "iniciais".

Por exemplo, eu quero ser capaz de filtro com base em: price, sqft, num_of_beds, e num_of_baths.

Como posso executar algo em JavaScript, como o pseudo-código abaixo:

var newArray = homes.filter(
    price <= 1000 & 
    sqft >= 500 & 
    num_of_beds >=2 & 
    num_of_baths >= 2.5 );

Observe que a sintaxe não precisa ser exatamente como acima. Este é apenas um exemplo.

JGreig
fonte
7
Isto parece quase idêntico ao stackoverflow.com/questions/1694717/…
Crescent Fresh
3
var json = { ... }JSON é uma notação textual para troca de dados. (Mais aqui.) Se você está lidando com o código-fonte JavaScript e não com uma string , não está lidando com o JSON.
TJ Crowder #
1
Não use eval. Geralmente, é uma prática ruim e pode causar problemas de desempenho. Nós apenas tivemos que nos livrar de um monte de pessoas em um projeto porque o processador estava travando.
SDH

Respostas:

718

Você pode usar o Array.prototype.filtermétodo:

var newArray = homes.filter(function (el) {
  return el.price <= 1000 &&
         el.sqft >= 500 &&
         el.num_of_beds >=2 &&
         el.num_of_baths >= 2.5;
});

Exemplo ao vivo:

Esse método faz parte do novo padrão ECMAScript 5th Edition e pode ser encontrado em quase todos os navegadores modernos.

Para o IE, você pode incluir o seguinte método para compatibilidade:

if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun /*, thisp*/) {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = [];
    var thisp = arguments[1];
    for (var i = 0; i < len; i++) {
      if (i in this) {
        var val = this[i];
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }
    return res;
  };
}
CMS
fonte
8
o que é / *, thisp * /?
precisa saber é o seguinte
2
@ JGreig: É apenas um comentário para indicar que um argumento opcional pode ser passado; o argumento não é especificado diretamente porque o padrão ECMA diz precisamente que esse método deve esperar apenas um argumento ( Array.prototype.filter.length == 1;). Quando você usa o segundo argumento, ele será usado como o thisvalor dentro da função de retorno de chamada.
CMS
1
@ CM, você tem certeza de que esse código funciona? Isso está retornando uma matriz vazia, mesmo quando eu defino o preço muito alto e os pés quadrados / camas / banheiros muito baixos. Tem certeza de que esse código funciona?
precisa saber é o seguinte
2
@JGreig: Sim, funciona, você deve verificar seus critérios, talvez possa postar um exemplo mais completo de seu JSON em pastie.org ou jsbin.com e os critérios que você está usando para filtrar, para que eu possa ajudá-lo melhor.
CMS
2
@CMS Seria bom se a resposta incluísse o fato de que uma nova matriz é retornada, a matriz original não é modificada. Mesmo que isso é dito no link fornecido, eu encontrar a resposta sem esta está incompleta ou imprecisa
GWorking
29

Você pode tentar usar uma estrutura como o jLinq - a seguir, uma amostra de código do uso do jLinq

var results = jLinq.from(data.users)
.startsWith("first", "a")
.orEndsWith("y")
.orderBy("admin", "age")
.select();

Para mais informações, você pode seguir o link http://www.hugoware.net/projects/jlinq

Rutesh Makhijani
fonte
26

Eu prefiro a estrutura de sublinhado. Ele sugere muitas operações úteis com objetos. Sua tarefa:

var newArray = homes.filter(
    price <= 1000 & 
    sqft >= 500 &
    num_of_beds >=2 & 
    num_of_baths >= 2.5);

pode ser substituído como:

var newArray = _.filter (homes, function(home) {
    return home.price<=1000 && sqft>=500 && num_of_beds>=2 && num_of_baths>=2.5;
});

Espero que seja útil para você!

JuliaCesar
fonte
Como essa coisa de sublinhado funciona e o que exatamente isso significa?
John Demetriou
4
Acabei de descobrir underscore-query( github.com/davidgtonge/underscore-query ) que usa sintaxe semelhante ao MongoDB para consultar matrizes javascript. Então, aqui você usaria_.query(homes, {price: {$lt:1000}, sqft: {$gte: 500}, num_of_beds: {$gte:2}, num_of_baths: {$gte: 2.5}}
protótipo
12

Estou surpreso que ninguém postou a resposta de uma linha:

const filteredHomes = json.homes.filter(x => x.price <= 1000 && x.sqft >= 500 && x.num_of_beds >=2 && x.num_of_baths >= 2.5);

... e para que você possa ler com mais facilidade:

const filteredHomes = json.homes.filter( x => 
  x.price <= 1000 && 
  x.sqft >= 500 && 
  x.num_of_beds >=2 && 
  x.num_of_baths >= 2.5
);
Lou Bagel
fonte
2
provavelmente porque é o mesmo que a resposta do CMS apenas com funções de seta
Erich
11

aqui está o violino que funciona bem no IE8 usando a função jquery MAP

http://jsfiddle.net/533135/Cj4j7/

json.HOMES = $.map(json.HOMES, function(val, key) {
    if (Number(val.price) <= 1000
            && Number(val.sqft) >= 500
            && Number(val.num_of_beds) >=2
            && Number(val.num_of_baths ) >= 2.5)
        return val;
});
Chetan Sandeep Renu
fonte
7

Você pode usar o jQuery.grep () desde o jQuery 1.0:

$.grep(homes, function (h) {
  return h.price <= 1000
    && h.sqft >= 500
    && h.num_of_beds >= 2
    && h.num_of_baths >= 2.5
});
Akira Yamamoto
fonte
7

Você poderia fazer isso com bastante facilidade - provavelmente existem muitas implementações que você pode escolher, mas esta é minha ideia básica (e provavelmente existe algum formato em que você pode iterar sobre um objeto com jQuery, não consigo pensar nisso agora):

function filter(collection, predicate)
{
    var result = new Array();
    var length = collection.length;

    for(var j = 0; j < length; j++)
    {
        if(predicate(collection[j]) == true)
        {
             result.push(collection[j]);
        }
    }

    return result;
}

E então você poderia invocar essa função da seguinte maneira:

filter(json, function(element)
{
    if(element.price <= 1000 && element.sqft >= 500 && element.num_of_beds > 2 && element.num_of_baths > 2.5)
        return true;

    return false;
});

Dessa forma, você pode chamar o filtro com base em qualquer predicado definido ou até filtrar várias vezes usando filtros menores.

Tejs
fonte
Altere "int length" para "var length" no primeiro trecho
Malaio Desai
4

Você pode implementar um método de filtro que atenda às suas necessidades, eis como:

function myfilter(array, test){
    var passedTest =[];
    for (var i = 0; i < array.length; i++) {
       if(test( array[i]))
          passedTest.push(array[i]);
    }

    return passedTest;
}

var passedHomes = myfilter(homes,function(currentHome){
     return ((currentHome.price <= 1000 )&& (currentHome.sqft >= 500 )&&(currentHome.num_of_beds >=2 )&&(currentHome.num_of_baths >= 2.5));
});

Espero que ajude!

user4620852
fonte
Minha condição de predicado muda de vários onclicks. Como posso armazenar os valores atuais do predicado e aplicá-lo à função myfilter sempre que eu quiser?
Malay Desai
3

Você deve verificar o OGX.List, que tem métodos de filtragem integrados e estende a matriz javascript padrão (e também agrupa, classifica e localiza). Aqui está uma lista de operadores suportados pelos filtros:

'eq' //Equal to
'eqjson' //For deep objects, JSON comparison, equal to
'neq' //Not equal to
'in' //Contains
'nin' //Doesn't contain
'lt' //Lesser than
'lte' //Lesser or equal to
'gt' //Greater than
'gte' //Greater or equal to
'btw' //Between, expects value to be array [_from_, _to_]
'substr' //Substring mode, equal to, expects value to be array [_from_, _to_, _niddle_]
'regex' //Regex match

Você pode usá-lo desta maneira

  let list = new OGX.List(your_array);
  list.addFilter('price', 'btw', 100, 500);
  list.addFilter('sqft', 'gte', 500);
  let filtered_list = list.filter();

Ou mesmo assim

  let list = new OGX.List(your_array);
  let filtered_list = list.get({price:{btw:[100,500]}, sqft:{gte:500}});

Ou como um forro

   let filtered_list = new OGX.List(your_array).get({price:{btw:[100,500]}, sqft:{gte:500}});
Eric
fonte
2

Ou você pode simplesmente usar $.each(que também funciona para objetos, não apenas matrizes) e criar uma nova matriz da seguinte forma:

var json = {
    'homes': [{
            "home_id": "1",
            "price": "925",
            "sqft": "1100",
            "num_of_beds": "2",
            "num_of_baths": "2.0",
        }, {
            "home_id": "2",
            "price": "1425",
            "sqft": "1900",
            "num_of_beds": "4",
            "num_of_baths": "2.5",
        },
        // ... (more homes) ...     
        {
            "home_id": "3-will-be-matched",
            "price": "925",
            "sqft": "1000",
            "num_of_beds": "2",
            "num_of_baths": "2.5",
        },
    ]
}

var homes = [];
$.each(json.homes, function(){
    if (this.price <= 1000
        && this.sqft >= 500
        && this.num_of_beds >= 2
        && this.num_of_baths >= 2.5
    ) {
        homes.push(this);
    }
});
Nux
fonte
2

Uso minha ruleOutfunção para filtrar objetos com base em valores específicos de propriedades indesejadas. Entendo que no seu exemplo você gostaria de usar condições em vez de valores, mas minha resposta é válida para o título da pergunta, portanto, gostaria de deixar meu método aqui.

function ruleOut(arr, filterObj, applyAllFilters=true) {    
    return arr.filter( row => {            
        for (var field in filterObj) {
            var val = row[field];
            if (val) {                    
                if (applyAllFilters && filterObj[field].indexOf(val) > -1) return false;                
                else if (!applyAllFilters) {                        
                    return filterObj[field].filter(function(filterValue){ 
                        return (val.indexOf(filterValue)>-1);
                    }).length == 0;                 
                }
            }
        }
        return true;
    });
}

Digamos que você tenha uma lista de atores como este:

let actors = [
  {userName:"Mary", job:"star", language:"Turkish"},
  {userName:"John", job:"actor", language:"Turkish"},
  {userName:"Takis", job:"star", language:"Greek"},
  {userName:"Joe", job:"star", language:"Turkish"},
  {userName:"Bill", job:"star", language:"Turkish"}
];

e você gostaria de encontrar todos os atores classificados como estrelas de Holywood, a nacionalidade deles não deve ser 'Inglês', 'Italiano', 'Espanhol', 'Grego', além de seu nome não ser 'Mary', 'Joe'. Exemplo de Bizzar, eu sei! De qualquer forma, com esse conjunto de condições, você criaria o seguinte objeto:

let unwantedFieldsFilter= { 
  userName: ['Mary', 'Joe'],    
  job: ['actor'],   
  language: ['English', 'Italian', 'Spanish', 'Greek']  
};

OK, agora se você ruleOut(actors, unwantedFieldsFilter)só conseguir

[{userName: "Bill", trabalho: "estrela", idioma: "turco"}]

E Bill é seu homem, já que o nome dele não é 'Mary', 'Joe', sua nacionalidade não está incluída em ['Inglês', 'Italiano', 'Espanhol', 'Grego'], além de ser uma Estrela!

Existe uma opção no meu método, que é applyAllFilterse é verdadeira por padrão. Se você tentasse ruleOut com esse parâmetro definido como false, isso funcionaria como uma filtragem 'OR' em vez de 'AND'. Exemplo: ruleOut(actors, {job:["actor"], language:["Italian"]}, false)você daria a todos que não são atores ou italianos:

[{userName: "Mary", trabalho: "estrela", idioma: "turco"},
{userName: "Takis", trabalho: "estrela", idioma: "grego"},
{userName: "Joe", trabalho: "estrela", idioma: "turco"},
{userName: "Bill", trabalho: "estrela", idioma: "turco"}]

JohnPan
fonte
1
var filterHome = homes.filter(home =>
  return (home.price <= 999 &&
         home.num_of_baths >= 2.5 &&
         home.num_of_beds >=2 &&
         home.sqft >= 998));
console.log(filterHome);

Você pode usar esta função lambda. Mais detalhes podem ser encontrados aqui, pois estamos filtrando os dados com base nas suas condições, que retornam verdadeiro ou falso, e os dados serão coletados em uma matriz diferente para que sua matriz real não seja modificada.

@JGreig Por favor, olhe para ele.

hardik beladiya
fonte
1
const x = JSON.stringify(data);
const y = 'search text';

const data = x.filter(res => {
        return(JSON.stringify(res).toLocaleLowerCase()).match(x.toLocaleLowerCase());
      });
Thức Trần
fonte
0
const state.contactList = [{
    name: 'jane',
    email: '[email protected]'
  },{},{},...]

const fileredArray = state.contactsList.filter((contactItem) => {
  const regex = new RegExp(`${action.payload}`, 'gi');
  return contactItem.nameProperty.match(regex) || 
    contactItem.emailProperty.match(regex);
});


// contactList: all the contacts stored in state
// action.payload: whatever typed in search field
anoNewb
fonte