Как отфильтровать массив объектов по атрибутам?

473

У меня есть следующий массив JavaScript объектов недвижимости дома:

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;

То, что я хотел бы сделать, это иметь возможность выполнить фильтр для объекта, чтобы вернуть подмножество «домашних» объектов.

Например, я хочу , чтобы иметь возможность фильтра на основе: price, sqft, num_of_beds, и num_of_baths.

Как я могу выполнить что-то в JavaScript, как псевдокод ниже:

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

Обратите внимание, что синтаксис не должен быть точно таким же, как указано выше. Это всего лишь пример.

JGreig
источник
7
Это выглядит почти идентично stackoverflow.com/questions/1694717/…
Crescent Fresh
3
var json = { ... }JSON - это текстовая запись для обмена данными. (Подробнее здесь.) Если вы имеете дело с исходным кодом JavaScript и не имеете дело со строкой , вы не имеете дело с JSON.
TJ Crowder
1
Не используйте eval. Как правило, это плохая практика и может вызвать проблемы с производительностью. Нам просто нужно было избавиться от группы тех, кто был в проекте, потому что процессор завис.
SDH

Ответы:

719

Вы можете использовать Array.prototype.filterметод:

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

Живой пример:

Этот метод является частью нового стандарта ECMAScript 5th Edition , и его можно найти практически во всех современных браузерах.

Для IE вы можете включить следующий метод для совместимости:

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
источник
8
что такое / *, thisp * /?
JGreig
2
@JGreig: Это просто комментарий, указывающий, что необязательный аргумент может быть передан, аргумент не указан напрямую, потому что стандарт ECMA точно говорит, что этот метод должен ожидать только один аргумент ( Array.prototype.filter.length == 1;). Когда вы используете второй аргумент, он будет использоваться как thisзначение внутри функции обратного вызова.
CMS
1
@CMS, ты уверен, что этот код работает? Это возвращает пустой массив, даже когда я установил цену очень высокой, а sqft / beds / baths - действительно низкой. Вы уверены, что этот код работает?
JGreig
2
@JGreig: Да, это работает, вы должны проверить свои критерии, может быть, вы могли бы опубликовать более полный пример вашего JSON на pastie.org или jsbin.com и критерии, которые вы используете для фильтрации, чтобы я мог помочь вам лучше.
CMS
2
@CMS Было бы хорошо, если бы ответ включал тот факт, что возвращается новый массив, исходный массив не изменяется. Несмотря на то, что это сказано в приведенной ссылке, я нахожу ответ без этого неполным или неточным
GWorking
29

Вы можете попробовать использовать фреймворк, такой как jLinq - ниже приведен пример использования jLinq.

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

Для получения дополнительной информации вы можете перейти по ссылке http://www.hugoware.net/projects/jlinq

Рутеш Махиджани
источник
26

Я предпочитаю основу Underscore. Он предлагает много полезных операций с объектами. Твое задание:

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

может быть перезаписано как:

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

Надеюсь, это будет полезно для вас!

JuliaCesar
источник
Как это работает, и что именно это означает?
Джон Деметриу
4
Только что обнаружил underscore-query( github.com/davidgtonge/underscore-query ), который использует MongoDB-подобный синтаксис для запроса массивов JavaScript. Так что здесь вы будете использовать_.query(homes, {price: {$lt:1000}, sqft: {$gte: 500}, num_of_beds: {$gte:2}, num_of_baths: {$gte: 2.5}}
прототип
12

Я удивлен, что никто не отправил ответ в одну строку:

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

... и чтобы вам было проще читать:

const filteredHomes = json.homes.filter( x => 
  x.price <= 1000 && 
  x.sqft >= 500 && 
  x.num_of_beds >=2 && 
  x.num_of_baths >= 2.5
);
Лу Бублик
источник
2
вероятно, потому что это так же, как ответ CMS только с функциями стрелок
Эрих
11

Вот рабочая скрипка, которая отлично работает в IE8, используя функцию 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;
});
Четан Сандип Рену
источник
7

Вы можете использовать jQuery.grep () начиная с 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
});
Акира Ямамото
источник
7

Вы могли бы сделать это довольно легко - вероятно, есть много реализаций, из которых вы можете выбирать, но это моя основная идея (и, возможно, есть какой-то формат, в котором вы можете перебирать объект с помощью jQuery, я просто не могу сейчас об этом думать):

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

И тогда вы можете вызвать эту функцию так:

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

Таким образом, вы можете вызывать фильтр на основе любого предиката, который вы определяете, или даже фильтровать несколько раз, используя фильтры меньшего размера.

Tejs
источник
Пожалуйста, измените "int length" на "var length" в первом фрагменте
Malay Desai
4

Вы можете самостоятельно реализовать метод фильтра, который соответствует вашим потребностям, вот как:

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

Надеюсь, поможет!

user4620852
источник
Мое условие предиката изменяется от различных кликов. Как сохранить текущие значения предикатов и применить их к функции myfilter, когда я захочу?
Малайский Десаи
3

Вы должны проверить OGX.List, который имеет встроенные методы фильтрации и расширяет стандартный массив javascript (а также группирование, сортировку и поиск). Вот список операторов, которые он поддерживает для фильтров:

'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

Вы можете использовать это таким образом

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

Или даже так

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

Или как один лайнер

   let filtered_list = new OGX.List(your_array).get({price:{btw:[100,500]}, sqft:{gte:500}});
Эрик
источник
2

Или вы можете просто использовать $.each(который также работает для объектов, а не только для массивов) и создать новый массив следующим образом:

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
источник
2

Я использую свою ruleOutфункцию для фильтрации объектов на основе определенных значений нежелательных свойств. Я понимаю, что в вашем примере вы хотели бы использовать условия вместо значений, но мой ответ действителен для заголовка вопроса, поэтому я бы хотел оставить здесь свой метод.

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

Скажем, у вас есть список таких актеров:

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

и вы хотели бы найти всех актеров, которые оценены как звезды Holywood, их национальность не должна быть одной из «английская», «итальянская», «испанская», «греческая», плюс их имя не будет принадлежать к «Mary», 'Джо'. Пример Биззара я знаю! В любом случае, с этим набором условий вы создадите следующий объект:

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

Хорошо, теперь, если вы ruleOut(actors, unwantedFieldsFilter)только получите

[{userName: "Bill", job: "star", язык: "Turkish"}]

И Билл - твой человек, так как его имя не одно из «Мэри», «Джо», его национальность не включена в [«английский», «итальянский», «испанский», «греческий»], плюс он - звезда!

В моем методе есть одна опция, которая applyAllFiltersпо умолчанию является истинной. Если вы попытаетесь исключить ruleOut с этим параметром, установленным как false, это сработает как фильтрация «ИЛИ» вместо «И». Пример: ruleOut(actors, {job:["actor"], language:["Italian"]}, false)вы получите всех, кто не является актером или итальянцем:

[{userName: "Mary", job: "star", язык: "Turkish"},
{userName: "Takis", job: "star", language: "Greek"},
{userName: "Joe", job: "star", язык: "Turkish"},
{userName: "Bill", job: "star", язык: "Turkish"}]

JohnPan
источник
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);

Вы можете использовать эту лямбда-функцию. Более подробную информацию можно найти здесь, так как мы фильтруем данные, исходя из того, что у вас есть условие, которое возвращает истину или ложь, и оно будет собирать данные в другой массив, поэтому ваш фактический массив не будет изменен.

@JGreig Пожалуйста, посмотрите на это.

хардик беладия
источник
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
источник
0
const state.contactList = [{
    name: 'jane',
    email: 'jane@gmail.com'
  },{},{},...]

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
источник