¿Cómo filtrar una matriz de objetos basada en atributos?

473

Tengo la siguiente matriz de JavaScript de objetos de inicio de bienes raíces:

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;

Lo que me gustaría hacer es poder realizar un filtro en el objeto para devolver un subconjunto de objetos "de inicio".

Por ejemplo, quiero ser capaz de filtro basado en: price, sqft, num_of_beds, y num_of_baths.

¿Cómo puedo realizar algo en JavaScript como el pseudocódigo siguiente?

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

Tenga en cuenta que la sintaxis no tiene que ser exactamente como la anterior. Esto es solo un ejemplo.

JGreig
fuente
77
Esto parece casi idéntico a stackoverflow.com/questions/1694717/…
Crescent Fresh
3
var json = { ... }JSON es una notación textual para el intercambio de datos. (Más información aquí.) Si se trata de código fuente JavaScript y no se trata de una cadena , no se trata de JSON.
TJ Crowder
1
No uses eval. Generalmente es una mala práctica y puede causar problemas de rendimiento. Simplemente tuvimos que deshacernos de un grupo de personas en un proyecto porque el procesador estaba bloqueado.
SDH

Respuestas:

719

Puedes usar el 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;
});

Ejemplo en vivo:

Este método es parte del nuevo estándar ECMAScript 5th Edition y se puede encontrar en casi todos los navegadores modernos.

Para IE, puede incluir el siguiente método de compatibilidad:

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
fuente
8
¿Qué es / *, thisp * /?
JGreig
2
@JGreig: es solo un comentario para indicar que se puede pasar un argumento opcional, el argumento no se especifica directamente porque el estándar ECMA dice con precisión que este método debe esperar solo un argumento ( Array.prototype.filter.length == 1;). Cuando use el segundo argumento, se usará como el thisvalor dentro de la función de devolución de llamada.
CMS
1
@CMS, ¿estás seguro de que este código funciona? Esto está devolviendo una matriz vacía, incluso cuando configuré el precio muy alto y los pies cuadrados / camas / baños muy bajos. ¿Estás seguro de que este código funciona?
JGreig
2
@JGreig: Sí, funciona, debería verificar sus criterios, tal vez podría publicar un ejemplo más completo de su JSON en pastie.org o jsbin.com y los criterios que está utilizando para filtrar, para que pueda ayudarlo mejor.
CMS
2
@CMS Sería bueno si la respuesta incluye el hecho de que se devuelve una nueva matriz, la matriz original no se modifica. A pesar de que esto se dice en el enlace proporcionado, encuentro que la respuesta sin esto es incompleta o inexacta
GWorking
29

Puede intentar usar un marco como jLinq: a continuación se muestra un código de muestra del uso de jLinq

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

Para obtener más información, puede seguir el enlace http://www.hugoware.net/projects/jlinq

Rutesh Makhijani
fuente
26

Prefiero el marco de subrayado. Sugiere muchas operaciones útiles con objetos. Tu tarea:

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

se puede sobrescribir como:

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

¡Espero que te sea útil!

JuliaCesar
fuente
¿Cómo funciona esta cosa de subrayado y qué significa exactamente?
John Demetriou
44
Recién descubierto underscore-query( github.com/davidgtonge/underscore-query ) que usa una sintaxis similar a MongoDB para consultar matrices javascript. Así que aquí usarías_.query(homes, {price: {$lt:1000}, sqft: {$gte: 500}, num_of_beds: {$gte:2}, num_of_baths: {$gte: 2.5}}
prototipo del
12

Me sorprende que nadie haya publicado la respuesta de una línea:

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

... y para que puedas leerlo más fácilmente:

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
fuente
2
probablemente porque es lo mismo que la respuesta de CMS solo con las funciones de flecha
Erich
11

Aquí está el violín de trabajo que funciona bien en IE8 usando la función 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
fuente
7

Puede usar jQuery.grep () desde 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
fuente
7

Podría hacerlo con bastante facilidad: probablemente hay muchas implementaciones entre las que puede elegir, pero esta es mi idea básica (y probablemente haya algún formato en el que pueda iterar sobre un objeto con jQuery, simplemente no puedo pensar en eso ahora):

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

Y luego podría invocar esta función así:

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

De esta manera, puede invocar el filtro en función del predicado que defina, o incluso filtrar varias veces utilizando filtros más pequeños.

Tejs
fuente
Cambie "int length" a "var length" en el primer fragmento
Malayo Desai
4

Puede implementar un método de filtro usted mismo que satisfaga sus necesidades, así es 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 eso ayude!

usuario4620852
fuente
Mi condición de predicado cambia de varios onclicks. ¿Cómo puedo almacenar los valores de predicado actuales y aplicarlos a la función myfilter cuando quiera?
Malayo Desai
3

Debe consultar OGX.List, que ha incorporado métodos de filtrado y extiende la matriz estándar de JavaScript (y también la agrupación, clasificación y búsqueda). Aquí hay una lista de operadores que admite para los 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

Puedes usarlo de esta manera

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

O incluso de esta manera

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

O como un trazador de líneas

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

O simplemente puede usar $.each(que también funciona para objetos, no solo matrices) y construir una nueva matriz de esta manera:

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
fuente
2

Uso mi ruleOutfunción para filtrar objetos basados ​​en valores de propiedad no deseados específicos. Entiendo que en su ejemplo le gustaría usar condiciones en lugar de valores, pero mi respuesta es válida para el título de la pregunta, por lo que me gustaría dejar mi método aquí.

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 tiene una lista de actores 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"}
];

y le gustaría encontrar a todos los actores calificados como estrellas de Holywood, su nacionalidad no debe ser una de 'inglés', 'italiano', 'español', 'griego', además su nombre no sería una de 'Mary', 'Joe'. Bizzar ejemplo, lo sé! De todos modos, con ese conjunto de condiciones crearía el siguiente objeto:

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

Bien, ahora si ruleOut(actors, unwantedFieldsFilter)solo tuvieras

[{userName: "Bill", trabajo: "star", idioma: "turco"}]

¡Y Bill es tu hombre, ya que su nombre no es uno de 'Mary', 'Joe', su nacionalidad no está incluida en ['inglés', 'italiano', 'español', 'griego'] y además es una estrella!

Hay una opción en mi método, que es applyAllFiltersy es verdadera por defecto. Si intentara descartar con este parámetro configurado como falso, funcionaría como un filtro 'OR' en lugar de 'AND'. Ejemplo: ruleOut(actors, {job:["actor"], language:["Italian"]}, false)te llevaría a todos los que no sean actores o italianos:

[{userName: "Mary", trabajo: "star", idioma: "turco"},
{userName: "Takis", trabajo: "star", idioma: "griego"},
{userName: "Joe", trabajo: "star", idioma: "turco"},
{userName: "Bill", trabajo: "star", idioma: "turco"}]

JohnPan
fuente
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);

Puedes usar esta función lambda. Puede encontrar más detalles aquí ya que estamos filtrando los datos en función de su condición que devuelve verdadero o falso y recopilará los datos en una matriz diferente para que su matriz real no se modifique.

@JGreig Por favor, míralo.

Hardik Beladiya
fuente
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
fuente
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
fuente