如何在JavaScript中过滤嵌套数据结构

时间:2016-08-23 13:50:56

标签: javascript json

假设我有一个嵌套的JavaScript对象,如下所示:

{
  "?xml": {
    "@version": "1.0",
    "@encoding": "UTF-8"
  },
  "Customer": {
    "@xmlns": "http://NamespaceTest.com/CustomerTypes",
    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
    "Name": {
      "#text": "Name1"
    },
    "DeliveryAddress": {
      "Line1": {
        "@xmlns": "http://NamespaceTest.com/CommonTypes",
        "#text": "Line11"
      },
      "Line2": {
        "@xmlns": "http://NamespaceTest.com/CommonTypes",
        "#text": "Line21"
      }
    }
  }
}

我想按名称定义属性列表,例如["?xml", "@xmlns"]并从结构中删除了这些属性,以便我得到以下输出:

{
  "Customer": {
    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
    "Name": {
      "#text": "Name1"
    },
    "DeliveryAddress": {
      "Line1": {
        "#text": "Line11"
      },
      "Line2": {
        "#text": "Line21"
      }
    }
  }
}

我知道我可以使用JSON.stringify()来完成,就像这样:

function replacer(key, value) {
  if (key === "?xml" || key === "@xmlns") {
    return undefined;
  }
  return value;
}

var filtered = JSON.parse( JSON.stringify( original, replacer ) );

但是我不喜欢结果首先转换为字符串然后必须被解析回对象。是否有一个函数可以过滤像JSON.stringify()这样的数据结构,但它返回一个对象而不是一个字符串?

2 个答案:

答案 0 :(得分:1)

据我所知,JavaScript中没有内置方法深度过滤嵌套数据结构,如JSON.stringify()给定的替换器回调。也就是说,编写自己的文章并不难:

function filterClone (data, replacer) {
    // return primitives unchanged
    if ( !(data instanceof Object) ) return data;

    // don't try to clone anything except plain objects and arrays
    var proto = Object.getPrototypeOf(data);
    if (proto !== Object.prototype && proto !== Array.prototype) return data;

    // it's a "plain object" or an array; clone and filter it!
    var clone = (proto === Object.prototype ? {} : []);
    for (var prop in data) {
        // safety: ignore inherited properties, even if they're enumerable
        if (!data.hasOwnProperty(prop)) continue;

        // call the replacer to let it modify or exclude the property
        var value = replacer(prop, data[prop]);

        if (value === undefined) continue;
        if (value instanceof Object) value = filterClone(value, replacer);
        clone[prop] = value;
    }
    return clone;
}

上面的递归函数将深度克隆任何“JSON-like”数据结构(即只包含普通{}对象,[]数组和基本类型(如数字,字符串和布尔值)的数据结构,使用与JSON.stringify()完全相同的替换回调来过滤它。也就是说,在您的问题中给出类似JSON的对象original,您可以像这样创建它的过滤副本:

function replacer (key, value) {
    if (key === "?xml" || key === "@xmlns") return undefined;
    else return value;
}

var filtered = filterClone(original, replacer);

请注意,此函数创建的“深度克隆”并不完美(因为it's hard to clone arbitrary objects in JavaScript),并且有一些需要注意的极端情况:

  • 此函数仅克隆直接继承自ObjectArray的对象(包括“普通对象”和使用{}[]创建的数组)。其他任何东西,包括原始值和任何其他类型的任何对象,都可以简单地复制到输出结构而不会被克隆。

    对于原始值,这是无害的,因为无论如何它们都是不可变的;但是,如果您的数据结构恰好包含Date个对象( 可变),则不会自动克隆这些对象。因此,修改克隆数据结构中的日期(使用例如setTime())可能会影响原始日期,反之亦然:

    var original = { "date" : new Date("1970-01-01T00:00:00.000Z") };
    var clone = filterClone( original, function (key, val) { return val } );
    console.log( original === clone );            // -> false
    console.log( original.date === clone.date );  // -> true (!)
    console.log( original.date.getTime() );       // -> 0
    clone.date.setYear(2016);
    console.log( original.date.getTime() );       // -> 1451606400000
    

    当然,您可以在替换器回调中解决此问题,例如:像这样:

    function replacer (key, value) {
        // this is how you clone a date in JS:
        if (value instanceof Date) value = new Date(value.getTime());
        return value;
    }
    
  • 此外,上面的filterClone()函数不会克隆对象中的任何非可枚举属性,并且具有非标准descriptor的任何(可枚举)属性将被标准属性替换克隆中没有getter,setter,write限制等。现在,使用{}对象文字语法创建的普通普通对象不应该具有任何此类花哨的属性描述符,但如果之后添加了任何描述符,请注意它们不会被克隆。 (显然,symbols也不会被克隆。)

  • 如果原始对象包含对同一个普通对象或数组的引用两次,它们将成为克隆中的单独对象/数组。例如:

    var sharedObject = {};
    var original = { "foo" : sharedObject, "bar" : sharedObject };
    var clone = filterClone( original, function (key, val) { return val } );
    console.log( original.foo === original.bar ); // -> true
    console.log( clone.foo === clone.bar );       // -> false
    
  • 此外,如果您的对象不是well founded(例如,如果它们包含对自己的引用),则filterClone()可能永远陷入无限递归(或直到它命中无论如何recursion limit。例如,这是创建无法使用filterClone()克隆的对象的一种简单方法:

    var foo = {};
    foo.foo = foo;
    
  • 最后,由于JSON.stringify()替换器回调接口(上面的代码忠实地遵循)使用undefined作为特殊值意味着“省略此属性”,因此无法正确克隆使用undefined包含filterClone()值的对象。克隆falsenull值的工作正常,但是:

    var original = { "foo" : undefined, "bar" : null };
    var clone = filterClone( original, function (key, val) { return val } );
    console.log( clone ); // -> Object { bar: null }
    

    (虽然在测试时,我确实在我的原始实现中发现了一个错误:显然,Object.getPrototypeOf(null)抛出了一个TypeError。在原型检查之前移动instanceof检查修复了这个问题。)

但是,除了最后一个问题之外,大多数其他JS deep clone implementations共享这些问题,包括JSON.parse( JSON.stringify( obj ) )。如上所述,深度克隆任意对象很难,尤其是像JavaScript那样没有标准方法将对象标记为可克隆的语言,并且在允许对象包含各种奇怪属性方面非常灵活。但是,对于“简单”对象(特别是包括通过解析有效JSON字符串返回的任何内容),此函数应该可以解决问题。

聚苯乙烯。当然,解决大多数这些问题的一种方法是进行过滤:

function filterInplace (data, replacer) {
    // don't try to filter anything except plain objects and arrays
    if ( !(data instanceof Object) ) return;
    var proto = Object.getPrototypeOf(data);
    if (proto !== Object.prototype && proto !== Array.prototype) return;

    // it's a "plain object" or an array; filter it!
    for (var prop in data) {
        // safety: ignore inherited properties, even if they're enumerable
        if (!data.hasOwnProperty(prop)) continue;

        // call the replacer to let it modify or exclude the property
        data[prop] = replacer(prop, data[prop]);

        if (data[prop] instanceof Object) filterInplace(data[prop], replacer);
        if (data[prop] === undefined) delete data[prop];
    }
}

此功能不返回任何内容;相反,它只是修改作为第一个参数传入的数据结构。它确实有一些自己的怪癖:

  • 它甚至不会尝试过滤除“普通”对象和数组之外的任何东西。
  • 它仍然打破了非完善的结构(就像任何递归解决方案一样,它没有明确地跟踪它已经访问过的对象)。
  • 出于同样的原因,如果同一个对象从数据结构中引用了两次,它将被过滤两次。 (但是,由于不涉及克隆,因此两个引用将始终指向同一对象。)
  • 它仍然不会过滤不可枚举的属性(或符号);他们将保持原样不受影响。
  • 它也可能无法使用非默认描述符正确过滤其他属性(例如,如果它们不可写,或者让getter或setter做一些有趣的事情)。

上述filterClone()filterInplace()函数之间在从数组末尾删除元素方面也存在细微差别:filterClone()会缩短数组,而filterInplace() }将始终在删除的元素所在的位置留下空值。在这种情况下,“正确”行为应该是什么,这有点争议; FWIW,JSON.stringify()也不会缩短数组。

答案 1 :(得分:0)

这是一个解决方案。

var json = '{"?xml": {"@version": "1.0","@encoding": "UTF-8"},"Customer": {"@xmlns": "http://NamespaceTest.com/CustomerTypes","@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance","Name": {"#text": "Name1"},"DeliveryAddress": {"Line1": {"@xmlns": "http://NamespaceTest.com/CommonTypes","#text": "Line11"},"Line2": {"@xmlns": "http://NamespaceTest.com/CommonTypes","#text": "Line21"}}}}';

var obj = JSON.parse(json);

function RemoveNameSpace(_obj) {
  var _this = _obj;
  for (var p in _this) {
    if (p == "?xml" || p == "@xmlns") {
      delete _this[p];
    }
    if (typeof(_this[p]) == 'object') {
      RemoveNameSpace(_this[p])
    }
  }
  return _this;
}

var newjson = JSON.stringify(RemoveNameSpace(obj));

console.log(newjson);