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

如何在JavaScript中过滤嵌套数据结构,javascript,json,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": {

假设我有一个嵌套的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"
      }
    }
  }
}
function replacer(key, value) {
  if (key === "?xml" || key === "@xmlns") {
    return undefined;
  }
  return value;
}

var filtered = JSON.parse( JSON.stringify( original, replacer ) );
function replacer (key, value) {
    if (key === "?xml" || key === "@xmlns") return undefined;
    else return value;
}

var filtered = filterClone(original, replacer);
function replacer (key, value) {
    // this is how you clone a date in JS:
    if (value instanceof Date) value = new Date(value.getTime());
    return value;
}
我想按名称定义属性列表,例如,
[“?xml”、“@xmlns”]
,并从结构中删除这些属性,以便获得以下输出:

{
  "Customer": {
    "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
    "Name": {
      "#text": "Name1"
    },
    "DeliveryAddress": {
      "Line1": {
        "#text": "Line11"
      },
      "Line2": {
        "#text": "Line21"
      }
    }
  }
}
我知道我可以用这样的方法来做:

{
  "?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"
      }
    }
  }
}
function replacer(key, value) {
  if (key === "?xml" || key === "@xmlns") {
    return undefined;
  }
  return value;
}

var filtered = JSON.parse( JSON.stringify( original, replacer ) );
function replacer (key, value) {
    if (key === "?xml" || key === "@xmlns") return undefined;
    else return value;
}

var filtered = filterClone(original, replacer);
function replacer (key, value) {
    // this is how you clone a date in JS:
    if (value instanceof Date) value = new Date(value.getTime());
    return value;
}
但我不喜欢先将结果转换为字符串,然后再解析回对象。是否有一个函数可以过滤像JSON这样的数据结构。stringify()可以,但它返回的是一个对象而不是字符串?

这里有一个解决方案

var json='{?xml:{@version:“1.0”,“@encoding:“UTF-8”},“Customer:{@xmlns:”http://NamespaceTest.com/CustomerTypes“,@xmlns:xsi”:”http://www.w3.org/2001/XMLSchema-instance“,”名称“:{”#文本“:”名称1“},“交货地址“{”Line1“:{”@xmlns:”http://NamespaceTest.com/CommonTypes“,“#text”:“Line11”},“Line2”:{“@xmlns”:"http://NamespaceTest.com/CommonTypes“,”文本“:“Line21”}}}}”;
var obj=JSON.parse(JSON);
函数RemoveNameSpace(_obj){
var _this=_obj;
对于(此中的var p){
if(p==“?xml”| p==“@xmlns”){
删除此[p];
}
if(typeof(_this[p])==“object”){
RemoveNameSpace(_this[p])
}
}
把这个还给你;
}
var newjson=JSON.stringify(RemoveNameSpace(obj));

console.log(newjson);
据我所知,JavaScript中没有内置的方法来深度过滤嵌套的数据结构,就像在给定替换回调时一样。也就是说,编写自己的代码并不难:

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”的数据结构(即仅由普通的
{}
对象、
[]
数组和数字、字符串和布尔等基本类型组成的数据结构),并使用与
JSON.stringify()完全相同的替换回调对其进行过滤
。也就是说,如您所问,给定一个类似JSON的对象
original
,您可以创建一个过滤后的副本,如下所示:

{
  "?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"
      }
    }
  }
}
function replacer(key, value) {
  if (key === "?xml" || key === "@xmlns") {
    return undefined;
  }
  return value;
}

var filtered = JSON.parse( JSON.stringify( original, replacer ) );
function replacer (key, value) {
    if (key === "?xml" || key === "@xmlns") return undefined;
    else return value;
}

var filtered = filterClone(original, replacer);
function replacer (key, value) {
    // this is how you clone a date in JS:
    if (value instanceof Date) value = new Date(value.getTime());
    return value;
}
请注意,此函数创建的“深度克隆”并不完美(因为),并且有一些极端情况需要注意:

  • 此函数仅克隆直接继承自
    对象
    数组
    (包括“普通对象”和使用
    {}
    []
    创建的数组)的对象。其他任何对象,包括基元值和任何其他类型的对象,都只需复制到输出结构中,而无需克隆

    对于基元值来说,这是无害的,因为它们无论如何都是不可变的;但是如果您的数据结构碰巧包含(比如)对象(它们是可变的),那么这些对象将不会被自动克隆。因此,修改克隆数据结构中的日期(使用例如
    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
    
    当然,您可以在replacer回调中解决此问题,例如:

    {
      "?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"
          }
        }
      }
    }
    
    function replacer(key, value) {
      if (key === "?xml" || key === "@xmlns") {
        return undefined;
      }
      return value;
    }
    
    var filtered = JSON.parse( JSON.stringify( original, replacer ) );
    
    function replacer (key, value) {
        if (key === "?xml" || key === "@xmlns") return undefined;
        else return value;
    }
    
    var filtered = filterClone(original, replacer);
    
    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()
    函数不会克隆对象中的任何不可枚举属性,克隆中的任何非标准(可枚举)属性将替换为标准属性(没有getter、setter、write restrictions等)object literal语法不应该有任何这样奇特的属性描述符,但如果您在之后添加了任何属性描述符,请注意它们将不会被克隆(显然,也不会被克隆)

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

    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
    
  • 此外,如果您的对象不是(例如,如果它们包含对自身的引用),
    filterClone()
    可能永远陷入无限递归中(或者直到它到达目标)。例如,这里有一个简单的方法来创建一个无法使用
    filterClone()克隆的对象。

  • 最后,由于
    JSON.stringify()
    replacer回调接口(上面的代码忠实地遵循该接口)将
    undefined
    用作特殊值,表示“忽略此属性”,因此无法使用
    filterClone()正确克隆包含
    undefined
    值的对象
    。克隆
    false
    null
    值可以正常工作,不过:

    var original = { "foo" : undefined, "bar" : null };
    var clone = filterClone( original, function (key, val) { return val } );
    console.log( clone ); // -> Object { bar: null }
    
    (然而,在测试时,我确实在我的原始实现中发现了一个bug:显然,
    Object.getPrototypeOf(null)
    抛出了一个类型错误。在原型检查之前移动
    instanceof
    检查解决了这个问题。)

尽管如此,除了最后一个问题外,这些问题中的大多数都是其他大多数问题的共同点,包括
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];
    }
}
此函数不返回任何内容;相反,它只是修改作为第一个参数传入的数据结构。它确实有一些独特之处:

  • 除了“普通”对象和数组,它甚至不尝试过滤任何东西
  • 它仍然会在没有良好基础的结构上中断(就像任何没有显式跟踪它已经访问过的对象的递归解决方案一样)
  • 出于同样的原因,如果同一对象从数据结构中被引用两次,它将被过滤两次。(但是,由于不涉及克隆,这两个引用将一直指向同一对象。)
  • 它仍然不能正确地过滤不可枚举的数据