JavaScript:如何创建一个Object并过滤这些属性?

时间:2009-11-07 22:53:38

标签: javascript html json filter

我有一系列像这样的房子,

{
    "homes" : [{
        "home_id"         : "1",
        "address"         : "321 Main St",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : "925",
        "sqft"            : "1100",
        "year_built"      : "2008",
        "account_type_id" : "2",
        "num_of_beds"     : "2",
        "num_of_baths"    : "2.0",
        "geolat"          : "32.779625",
        "geolng"          : "-96.786064",
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/"
    }],
    ..........
}

我想提供3种不同的搜索方法。

如何返回此家庭区域数组的子集:

  • X和Y之间的price
  • bathrooms> = Z
  • bedrooms == A或== B或== C
  • 的#

例如,我如何创建psuedo代码,如:

homes.filter {price >= 150000, price <= 400000, bathrooms >= 2.5, bedrooms == 1 | bedrooms == 3}

13 个答案:

答案 0 :(得分:15)

Javascript 1.6(FF,基于Webkit)具有内置的Array.filter功能,因此无需重新发明轮子。

result = homes.
  filter(function(p) { return p.price >= 150000 }).
  filter(function(p) { return p.price <= 400000 }).
  filter(function(p) { return p.bathrooms >= 2.5 }) etc

有关msie后备的信息,请参阅the page linked above

答案 1 :(得分:5)

查看JSONPath http://code.google.com/p/jsonpath/

像jsonPath(json,“$ .. homes [?(@。price&lt; 400000)]”)。toJSONString()应该有效。

JAQL对于过滤JSON也很有趣。 http://www.jaql.org/

答案 2 :(得分:5)

你走了:

var filteredArray = filter(myBigObject.homes, {

    price: function(value) {
        value = parseFloat(value);
        return value >= 150000 && value <= 400000;
    },

    num_of_baths: function(value) {
        value = parseFloat(value);
        return value >= 2.5;
    },

    num_of_beds: function(value) {
        value = parseFloat(value);
        return value === 1 || value === 3;
    }

});

filter函数:

function filter( array, filters ) {

    var ret = [],
        i = 0, l = array.length,
        filter;

    all: for ( ; i < l; ++i ) {

        for ( filter in filters ) {
            if ( !filters[filter](array[i][filter]) ) {
                continue all;
            }
        }

        ret[ret.length] = array[i];

    }

    return ret;

}

答案 3 :(得分:2)

查看$.grep库中的jQuery函数。即使您不使用该库,您也可以查看代码并查看它们是如何完成您要执行的任务的。

编辑:这是一些代码:

 function filter(houses, filters) // pass it filters object like the one in @J-P's answer
 {
      retArr = $.grep(houses, 
           function(house)
           {
                for(var filter in filters)
                {
                     function test = filters[filter];
                     if(!test(house)) return false;
                }
                return true;
           });

      return retArr;
 }

答案 4 :(得分:2)

Array.prototype.select = function(filter) {

    if (!filter) return this;

    var result = [], item = null;
    for (var i = 0; i < this.length; i++) {
        item = this[i];
        if (filter(item)) {
            result.push(item);
        }
    }

    return result;
}

function filterHomes(homes) {
    var a = 1, b = 2, c = 3, x = 4, y = 5, z = 6;
    return homes.select(function(item) {
        return between(item.price, x, y) && item.num_of_baths >= z && inArray(item.num_of_beds, [a, b, c]);
    });
}

function between(value, min, max) {
    return value >= min && value <= max;
}

function inArray(value, values) {
    for (var i = 0; i < values.length; i++) {
        if (value === values[i]) return true;
    }
    return false;
}

答案 5 :(得分:2)

我喜欢这个问题,所以我正在拍摄它。如何使用“链式”代码,您拥有的对象返回自己,以及一些JavaScript DOM框架。

我正在调用你的对象MyObj

MyObj.filter('home.price >= 150000')
     .filter('home.price <= 400000')
     .filter('home.num_of_baths >= 2.5')
     .filter('home.num_of_beds == 1 || home.bedrooms == 3');

这是源代码,这个例子有效。

var MyObj = {
    filter : function(rule_expression) {
        var tmpHomes = [];
        var home = {};
        for(var i=0;i<this.homes.length;i++) {
            home = this.homes[i];
            if (eval(rule_expression)) {
                tmpHomes.push(home);
            }
        }
        this.homes = tmpHomes;
        return this;
    },
    homes: [
    {
        "home_id"         : 1,
        "address"         : "321 Main St",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 300000,
        "sqft"            : 1100,
        "year_built"      : 2008,
        "account_type_id" : 2,
        "num_of_beds"     : 1,
        "num_of_baths"    : 2.5,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    },
    {
        "home_id"         : 2,
        "address"         : "555 Hello World Way",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 200000,
        "sqft"            : 900,
        "year_built"      : 1999,
        "account_type_id" : 2,
        "num_of_beds"     : 1,
        "num_of_baths"    : 1.0,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    },
    {
        "home_id"         : 3,
        "address"         : "989 Foo St",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 80000,
        "sqft"            : 1100,
        "year_built"      : 2003,
        "account_type_id" : 2,
        "num_of_beds"     : 3,
        "num_of_baths"    : 3,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    },
    {
        "home_id"         : 4,
        "address"         : "1560 Baz Rd",
        "city"            : "Dallas",
        "state"           : "TX",
        "zip"             : "75201",
        "price"           : 100000,
        "sqft"            : 1100,
        "year_built"      : 2008,
        "account_type_id" : 2,
        "num_of_beds"     : 3,
        "num_of_baths"    : 1.5,
        "geolat"          : 32.779625,
        "geolng"          : -96.786064,
        "photo_id"        : "14",
        "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/foo.jpg"
    }
    ]
};

答案 6 :(得分:2)

有趣的是,这些答案中的大部分都没有做任何事情,只是尝试更优雅地迭代阵列,而这甚至不是GeorgeG所需要的。

假设你有一个巨大的JSON,你想要快速的东西。迭代不是那样的。你需要jOrder(http://github.com/danstocker/jorder),它在1000行表上的搜索速度比迭代过滤快100倍。表越大,比率越高。

这是你做的。

由于jOrder一次不能处理多个不等式滤波器,你必须分三步完成,但速度仍然会更快。

首先,根据您的原始数据创建一个jOrder表,并在其上添加价格指数:

var table = jOrder(data.homes)
    .index('price', ['price'], { ordered: true, grouped: true, type: jOrder.number });

您可以在以后的搜索中重复使用此表。现在开始获取价格筛选结果,并将其包装在另一个jOrder表中,最后添加一个浴室索引:

var price_filtered = jOrder(table.where([{ price: { lower: 150000, upper: 400000 } }]))
    .index('bathrooms', ['num_of_baths'], { ordered: true, grouped: true, type: jOrder.number });

然后我们继续为浴室做同样的事情,在卧室上添加一个索引,这不需要订购,因为我们不会在这里使用不等式过滤器:

var bath_filtered = jOrder(price_filtered.where([{ num_of_baths: { lower: 2.5 } }]))
    .index('bedrooms', ['num_of_beds'], { grouped: true });

最后,您将获得完全过滤的设置:

var filtered = jOrder(bath_filtered.where([{ num_of_beds: 1 }, { num_of_beds: 3 }]));

当然,你可以将所有这些包装成一个带有三个参数的函数:

  • { price: { lower: 150000, upper: 400000 } }
  • { num_of_baths: { lower: 2.5 } }
  • [{ num_of_beds: 1 }, { num_of_beds: 3 }]

并返回filtered

答案 7 :(得分:1)

Underscore.js库适用于这些任务。它存在时使用本机JavaScript命令。我已经设法使用这个库获得了许多迭代循环。

Bonus,最新版本可以链接,就像jQuery一样。

答案 8 :(得分:1)

我已经多次修改了这个答案,如果对修订感兴趣,人们可以查看维基,但这是我提出的最终解决方案。它与此处发布的许多其他内容非常相似,关键的区别在于我扩展了Array.prototype包裹Array.prototype.filter函数,以便在数组元素的范围内评估测试语句,而不是数组。

我看到这个解决方案的主要优点而不是简单地直接使用filter方法,它允许您编写测试this[key]值的通用测试。然后,您可以创建与特定测试关联的通用表单元素,并使用要过滤的对象中的元数据(在本例中为house)将表单元素的特定实例与键相关联。这不仅使代码更具可重用性,而且我认为它实际上以编程方式构建查询更加直接。此外,由于您可以定义通用表单元素(例如,对于不同的值进行多选,或者为范围设置一组下拉列表),您还可以创建更强大的查询接口,其中可以动态注入其他表单元素,从而允许用户创建复杂和定制的查询。以下是代码分解为:

在执行任何操作之前,您应首先检查filter函数是否存在,如果不像其他解决方案中所述那样扩展Array.prototype。然后扩展Array.prototype以包装filter函数。我已经做到这一点,以便参数是可选的,如果有人想要使用一个由于某些原因不采取任何测试函数,我也试图包含错误来帮助你实现代码:

Array.prototype.filterBy = function(testFunc, args)
{
    if(args == null || typeof args != 'object') args = [args];
    if(!testFunc || typeof testFunc != 'function') throw new TypeError('argument 0 must be defined and a function');
    return this.filter(function(elem)
        {
        return testFunc.apply(elem, args);
    });
};

这需要一个测试函数和一个参数数组,并定义一个内联函数作为Array.prototype.filter函数的回调函数,该函数调用function.prototype.apply来执行数组元素范围内的测试函数使用指定的参数进行测试。然后,您可以编写一套通用测试函数,如下所示:

testSuite = {

     range : function(key, min, max)
     {
          var min = parseFloat(min);
          var max = parseFloat(max);
          var keyVal = parseFloat(this[key]);
          if(!min || !max|| !keyVal) return false;
          else return keyVal >= min && keyVal <= max;
     },

     distinct : function(key, values)
     {
          if(typeof key != 'string') throw new TypeError('key must be a string');
          if(typeof values != 'object') values = [values];
          var keyVal = this[key];
          if(keyVal == undefined) return false;


          for(var i in values)
          {
             var value = values[i];
             if(typeof value == 'function') continue;
             if(typeof value == 'string')
             {
                  if(keyVal.toString().toLowerCase() == value.toLowerCase()) return true;
                  else continue; 
             }
             else
             {
                 keyVal = parseFloat(keyVal);
                 value = parseFloat(value);
                 if(keyVal&&value&&(keyVal==value)) return true;
                 else continue;

            }
          }
          return false;
     }
};

这些只是与您的问题中的需求相关联的测试函数,但是您可以创建其他更复杂的函数,例如针对正则表达式执行测试字符串值,或者测试不是简单数据类型的键值。

然后你像这样扩展包含houses数组的对象(我称之为housesData,在代码中调用它):

 housesData.filterBy = function(tests)
 {
      ret = this.homes.slice(0);
      if(tests)
      {
           for(var i in tests)
           {
               var test = tests[i];
               if(typeof test != 'object') continue;
               if(!test.func || typeof test.func != 'function') throw new TypeError('argument 0 must be an array or object containing test objects, each with a key "func" of type function');
               else ret = ret.filterBy(test.func, test.args ? test.args : []);
           }
      }
      return ret;
 }

然后,您可以像这样调用此函数,以使用上面定义的泛型函数获取结果集:

 result = housesData.filterBy([{func:range,args:['price','150000','400000'],
                      {func:distinct,args:['num_of_bedsf',[1, 2, 3]]}]);

我实际上预测使用它的方法是将我前面提到的通用表单元素序列化为测试对象的数组或散列映射。这是一个我用来测试这段代码的简单例子(我使用jQuery,因为它更容易,所有以前的代码都在example.js中,还有@ artlung的虚拟家庭数组):

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE html 
      PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:myNS="http://uri.for.your.schema" xml:lang="en" lang="en">
<head>
    <script src="jquery-1.3.2.js" type="text/javascript" language="javascript"></script>
    <script src="example.js" type="text/javascript" language="javascript"></script>
    <script type="text/javascript" language="javascript">
        runQuery = function(event)
        {
            var tests = [];
            $('#results').attr('value', '');;
            controls = $('#theForm > fieldset');
            for(var i = 0; i < controls.length ; i++)
            {
                var func;
                var args = [];
                control = controls.eq(i);
                func = testSuite[control.attr('myNS:type')];
                args.push(control.attr('myNS:key'));
                var inputs = $('input', control);
                for(var j=0; j< inputs.length; j++)
                {
                    args.push(inputs[j].value);
                }
                tests.push({func:func,args:args});
            }
            result = housesData.filterBy(tests);
            resultStr = '';
            for(var i = 0; i < result.length; i++)
            {
                resultStr += result[i]['home_id'];
                if(i < (result.length -1)) resultStr += ', ';
            }
            $('#results').attr('value', resultStr);
        }
    </script>
</head>
<body>
  <form id="theForm" action="javascript:null(0)">
    <fieldset myNS:type="range" myNS:key="price">
        <legend>Price:</legend>
        min: <input type="text" myNS:type="min"></input>
        max: <input type="text" myNS:type="max"></input>
    </fieldset>
    <fieldset myNS:type="distinct" myNS:key="num_of_beds">
        <legend>Bedrooms</legend>
        bedrooms: <input type="text" myNS:type="value"></input>
    </fieldset>
    <button onclick="runQuery(event);">Submit</button>
  </form>
  <textarea id="results"></textarea>
    </body>
 </html>

答案 9 :(得分:0)

o = ({
'homes' : 
[{
    "home_id"         : "1",
    "address"         : "321 Main St",
    "city"            : "Dallas",
    "state"           : "TX",
    "zip"             : "75201",
    "price"           : "20000",
    "sqft"            : "1100",
    "year_built"      : "2008",
    "account_type_id" : "2",
    "num_of_beds"     : "3",
    "num_of_baths"    : "3.0",
    "geolat"          : "32.779625",
    "geolng"          : "-96.786064",
    "photo_id"        : "14",
    "photo_url_dir"   : "\/home_photos\/thumbnail\/2009\/06\/10\/"
}
]})

Array.prototype.filterBy = function( by ) {
    outer: for ( 
    var i = this.length,
        ret = {},
        obj; 
        i--; 
    ) 
    {
    obj = this[i];
    for ( var prop in obj ) {

        if ( !(prop in by) ) continue

        if ( by[prop](obj[prop]) ) {
        ret[prop] = obj[prop]
        }

    }
    }

    return ret;
}

var result = o.homes.filterBy({
    price:function(price) {
        price = parseFloat(price)
        return price >= 15000 && price <=40000
    },
    num_of_baths:function(bathroom){
        bathroom = parseFloat(bathroom)
        return bathroom > 2.5
    },
    num_of_beds:function(bedroom){
        bedroom = parseFloat(bedroom)
        return bedroom === 1 || bedroom === 3
    }
});

for ( var p in result ) alert(p + '=' + result[p])

答案 10 :(得分:0)

我同意不重新发明轮子,只使用本机阵列过滤器方法。

通过使用对象检测(filter

,Mozilla页面上显示的函数定义将添加if (!Array.prototype.filter)(如果它丢失,无论浏览器如何)

建议eval和/或多个for循环的任何方法均为slow and potentially unsafe

你没有这么说,但我假设价值来自用户输入(表格)所以我会做类似的事情:

var filtered_results = obj.homes
    .filter(function (home)
    {
        return parseInt(home.price) >= document.getElementsByName('price_gt')[0].value;
    })
    .filter(function (home)
    {
        return parseInt(home.price) <= document.getElementsByName('price_lt')[0].value;
    })
    .filter(function (home)
    {
        return parseFloat(home.num_of_baths) >= document.getElementsByName('baths_gt')[0].value;
    });

更好的是,不要在列表中迭代N次,然后执行:

var filtered_results = obj.homes.filter(function (home)
{
    return (
        parseInt(home.price) >= document.getElementsByName('price_gt')[0].value &&
        parseInt(home.price) <= document.getElementsByName('price_lt')[0].value &&
        parseFloat(home.num_of_baths) >= document.getElementsByName('baths_gt')[0].value
    );
});

我知道这与请求的语法不匹配,但这种方法更快,更灵活(不使用eval或硬编码值)。

答案 11 :(得分:0)

Ryan Lynch - 如果您使用字符串进行比较('值运算符比较'),您可以使用纯JavaScript(值运算符比较)。逻辑不灵活性是相同的,只是速度和安全性较低(通过eval())。

我的例子缺乏灵活性来自于知道哪些领域(价格,浴室等)以及我们感兴趣的运营商,但原始海报的要求列出了具体的字段和比较(价格&lt; X,浴室&gt; Y等。)

GeorgeG - 如果您感兴趣,并且我对来自表单的过滤器值的假设是正确的,我可以为每个用户指定的值编写更通用的“按请求过滤结果”方法但是我会等待确认,如果可能的话,我会等一个代码示例。

答案 12 :(得分:-1)

我假设您希望能够在homes数组上调用过滤器方向的语法糖生成仅包含过滤器接受结果的新数组。

使用对象原型在接受参数过滤器哈希的数组对象上定义过滤器函数。然后,您可以轻松地使用该哈希生成并返回与所需属性匹配的新数组。