【问题标题】:Javascript - How to delete element from deeply nested array by valueJavascript - 如何按值从深度嵌套的数组中删除元素
【发布时间】:2021-08-21 04:10:26
【问题描述】:

我有类似的 JSON:

[
  {
    "property": "Foo",
    "address": "Foo Address",
    "debitNote": [
      {
        "title": "Debit A Foo",
        "desc": "Desc Debit Foo",
        "listDebit": [
          {
            "id": "IP-1A1",
            "amount": "273000"
          },
          {
            "id": "IP-1A2",
            "amount": "389000"
          },
          {
            "id": "IP-1A3",
            "amount": "382000"
          },
          {
            "id": "IP-1A4",
            "amount": "893000"
          },
          {
            "id": "IP-1A5",
            "amount": "1923000"
          }
        ]
      },
      {
        "title": "Debit B Foo",
        "desc": "Desc Debit B Foo",
        "listDebit": [
          {
            "id": "IP-1B1",
            "amount": "120000"
          },
          {
            "id": "IP-1B2",
            "amount": "192000"
          }
        ]
      }
    ]
  },
  {
    "property": "Bar",
    "address": "Address Bar",
    "debitNote": [
      {
        "title": "Debit A Bar",
        "desc": "Desc Bar",
        "listDebit": [
          {
            "id": "IP-1C1",
            "amount": "893000"
          },
          {
            "id": "IP-1C2",
            "amount": "1923000"
          }
        ]
      },
      {
        "title": "Debit B Bar",
        "desc": "Desc Debit B Bar",
        "listDebit": [
          {
            "id": "IP-1D1",
            "amount": "192000"
          }
        ]
      }
    ]
  }
]

我需要从这些 json 中修复 2 个问题:

  1. 检查value是否与JSON中的id匹配
  2. 如果通过值找到JSONid 并且id 索引是最新索引,则删除元素

我可以用当前代码处理1st task

let json = [{"property":"Foo","address":"Foo Address","debitNote":[{"title":"Debit A Foo","desc":"Desc Debit Foo","listDebit":[{"id":"IP-1A1","amount":"273000"},{"id":"IP-1A2","amount":"389000"},{"id":"IP-1A3","amount":"382000"},{"id":"IP-1A4","amount":"893000"},{"id":"IP-1A5","amount":"1923000"}]},{"title":"Debit B Foo","desc":"Desc Debit B Foo","listDebit":[{"id":"IP-1B1","amount":"120000"},{"id":"IP-1B2","amount":"192000"}]}]},{"property":"Bar","address":"Address Bar","debitNote":[{"title":"Debit A Bar","desc":"Desc Bar","listDebit":[{"id":"IP-1C1","amount":"893000"},{"id":"IP-1C2","amount":"1923000"}]},{"title":"Debit B Bar","desc":"Desc Debit B Bar","listDebit":[{"id":"IP-1D1","amount":"192000"}]}]}]

function findID(array, value){
  return array.some((item)=>{
    if(item.id == value) {
      return true
    }
    return Object.keys(item).some(x=>{
      if(Array.isArray(item[x])) return findID(item[x], value)
    })
  })
}
  
console.log(findID(json, 'IP-1D1'))
console.log(findID(json, 'IP-1A1'))
console.log(findID(json, 'IP-1B1'))
console.log(findID(json, 'IP-1Z1'))

但我坚持第二个任务,预期结果如下:

console.log(removeID(json, 'IP-1A5')) // {"id": "IP-1A5", "amount": "1923000"} removed
console.log(removeID(json, 'IP-1A3')) // couldn't remove, because there's {"id": "IP-1A4", "amount": "893000"} after id 'IP-1A3'

有人建议解决我的第二个问题吗?

【问题讨论】:

  • 您需要更具体地说明什么是“最新”。是从索引 5 开始的任何数字,还是更复杂的数字?
  • 嵌套级别有多深?是问题中显示的还是可以更深入?
  • @Tibrogargan,由于不保证 id 是递增的,我可以说它只是任何数量的 id
  • @decpk 目前嵌套深度的数组就像 JSON 创建的一样
  • 这没有帮助。您需要提供一些定义明确的机制来确定什么是最新的?显然,“id”实际上是两条(或更多条)信息组合在一起产生一个标识符,但公式是什么?它总是一个 5 个字母的前缀后跟一个数字,还是可以是“后面的任何内容 - 后跟一些数字,后跟一个或多个字母,后跟一个数字”。即ABCDEFG13854761324-7864784JHKFLKHFJG0 可能是合法的

标签: javascript json recursion


【解决方案1】:
function removeID(array, value) {
  for (let dn of json.map((node) => node.debitNote)) {
    for (let lb of dn.map((n) => n.listDebit)) {
      for (let i = 0; i < lb.length; i++) {
        if (lb[i].id === value) {
          return lb.splice(i, 1);
        }
      }
    }
  }

  return {};
}

https://codepen.io/chriswong979/pen/bGqLZOL?editors=1111

【讨论】:

  • 解释你的代码做什么会很好,乍一看它看起来很神秘,同时使用for循环和函数构造,如map
【解决方案2】:
// Get the id's path
function findPath(array, val) {
  for (let i = 0; i < array.length; i++) {
    const item = array[i];
    if (item.id !== val) {
      const x = Object.keys(item).find(x => Array.isArray(item[x]))
      if (x) {
        const path = findPath(item[x], val);
        if (path) {
          path.unshift(x);
          path.unshift(i);
          return path
        }
      }
    } else {
      return [i];
    }
  }
}
// Use the path to remove the item from json
function removeLatestIndexId(path, json) {
  const result = path.slice(0, -1).reduce((pre, next) => {
    return pre[next]
  }, json);
  const idIndex = path[path.length - 1]
  if (idIndex === result.length - 1) {
    result.splice(idIndex, 1)
  }
}

https://codepen.io/shuizy/pen/bGqLXoQ

您可以使用findPath 在json 中获取id 的路径,并使用该路径来帮助您解决问题#2。 (其实如果你有路径你也可以解决问题#1)

【讨论】:

    【解决方案3】:

    目前还不清楚你说的最新id是什么意思。但我们也许可以将问题分解,使决策与其他逻​​辑隔离。

    我们可以开始编写具有任意嵌套数组和对象的 Javascript 结构的通用遍历,根据给定节点是否匹配我们提供的测试来构建新对象,然后在删除元素的基础上构建匹配给定的 id。它可能看起来像这样:

    const deepFilter = (pred) => (o) =>
      Array .isArray (o)
        ? o .filter (x => pred (x)) .map (deepFilter (pred))
      : Object (o) === o
        ? Object .fromEntries (
            Object .entries (o) .flatMap (([k , v]) => pred (v) ? [[k, deepFilter (pred) (v)]] : [])
          ) 
      : o
    
    const deepReject = (pred) =>
      deepFilter ((x) => ! pred (x))
    
    const removeById = (target) => deepReject (({id}) => target == id)
    

    deepFilter 接受一个谓词函数并返回一个接受此类嵌套对象/数组的函数,并返回它的副本,包括与该谓词匹配的节点。

    deepReject 建立在deepFilter 之上,只是颠倒了谓词的含义。

    removeById 构建在 deepReject 之上,接受目标 id 并返回一个接受对象的函数,返回它的副本而不返回那些 id 与该目标匹配的节点。 p>

    现在,如果我们有一个函数 (lastMatch) 来告诉我们一个 id 是否是我们输入中最后一个匹配的,我们可以这样编写我们的 main 函数:

    const removeIdIfLast = (id) => (input) => 
      lastMatch (id) (input)
        ? removeById (id) (input)
        : input
    

    我将猜测一下“最新索引”可能意味着什么。但请注意,这个答案的其余部分涉及一个很可能是错误的猜测。

    我假设对于像'IP-1A4' 这样的id,“最后”的含义与最后的4 有关,也就是说,'IP-1A1''IP-1A3' 在它之前,而@987654333 @ 和 'IP-1A101' 在它之后。但是我们只与前缀为 IP-1A 的 id 进行比较。

    所以,再次希望我们有另一个函数可供我们使用 (matchingIds),我们可以编写一个 lastMatch 函数,通过对文档中与我们的目标匹配的 id 进行排序来找到最大 id,然后按降序排序提取数字部分并减去,然后取排序列表的第一个元素。 (排序不是找到最大值的最有效方法。如果这些列表可能很长,您可能想要编写此函数的更高效版本;这一点也不难,但它会让我们走得更远。)

    const lastMatch = (id) => (input) =>
      id == matchingIds (id) (input) .sort (
        (a, b, x = a .match (/.+(\d+)$/) [1], y = b .match (/.+(\d+)$/) [1]) => y - x
      ) [0]
    

    我们可以基于这个想法写matchingIds,如果我们只有一个函数来收集我们数据中的所有id。它可能看起来像这样:

    const matchingIds = (id, prefix = id .match (/(.+)\d+$/) [1]) => (input) =>
      findIds (input) .filter (id => id .match (/(.+)\d+$/) [1] == prefix)
    

    这是在假设输入中的所有 id 都具有这种以数字字符串结尾的结构。如果这个假设不正确,很可能需要一个更复杂的版本。

    那么我们应该怎么写findIds?嗯,一个简单的方法会模仿 deepFilter 的设计,看起来像这样:

    const findIds = (o) => 
      Array.isArray (o)
        ? o .flatMap (findIds)
      : Object (o) === o
        ? Object .entries (o) .flatMap (([k, v]) => k == 'id' ? [v, ...findIds (v)] : findIds (v))
      : []
    

    但我看到该函数中潜伏着一个有用的通用抽象,所以我可能会以这种方式在此基础上构建它:

    const fetchAll = (prop) => (o) => 
      Array.isArray (o)
        ? o .flatMap (findIds)
      : Object (o) === o
        ? Object .entries (o) .flatMap (
            ([k, v]) => k == prop ? [v, ... fetchAll (prop) (v)] : fetchAll (prop) (v)
          )
      : []
    
    const findIds = fetchAll ('id')
    

    将所有这些放在一起,这是一个基于上述假设的解决方案:

    const deepFilter = (pred) => (o) =>
      Array .isArray (o)
        ? o .filter (x => pred (x)) .map (deepFilter (pred))
      : Object (o) === o
        ? Object .fromEntries (
            Object .entries (o) .flatMap (([k , v]) => pred (v) ? [[k, deepFilter (pred) (v)]] : [])
          ) 
      : o
    
    const deepReject = (pred) =>
      deepFilter ((...args) => ! pred (...args))
    
    const removeById = (target) => deepReject (({id}) => target == id)
    
    const fetchAll = (prop) => (o) => 
      Array.isArray (o)
        ? o .flatMap (findIds)
      : Object (o) === o
        ? Object .entries (o) .flatMap (([k, v]) => k == prop ? [v, ... fetchAll (prop) (v)] : fetchAll (prop) (v))
      : []
    
    const findIds = fetchAll ('id')
    
    const matchingIds = (id, prefix = id .match (/(.+)\d+$/) [1]) => (input) =>
      findIds (input) .filter (id => id .match (/(.+)\d+$/) [1] == prefix)
      
    const lastMatch = (id) => (input) =>
      id == matchingIds (id) (input) .sort (
        (a, b, x = a .match (/.+(\d+)$/) [1], y = b .match (/.+(\d+)$/) [1]) => y - x
      ) [0]
    
    const removeIdIfLast = (id) => (input) => 
      lastMatch (id) (input)
        ? removeById (id) (input)
        : input
    
    const input = [{property: "Foo", address: "Foo Address", debitNote: [{title: "Debit A Foo", desc: "Desc Debit Foo", listDebit: [{id: "IP-1A1", amount: "273000"}, {id: "IP-1A2", amount: "389000"}, {id: "IP-1A3", amount: "382000"}, {id: "IP-1A4", amount: "893000"}, {id: "IP-1A5", amount: "1923000"}]}, {title: "Debit B Foo", desc: "Desc Debit B Foo", listDebit: [{id: "IP-1B1", amount: "120000"}, {id: "IP-1B2", amount: "192000"}]}]}, {property: "Bar", address: "Address Bar", debitNote: [{title: "Debit A Bar", desc: "Desc Bar", listDebit: [{id: "IP-1C1", amount: "893000"}, {id: "IP-1C2", amount: "1923000"}]}, {title: "Debit B Bar", desc: "Desc Debit B Bar", listDebit: [{id: "IP-1D1", amount: "192000"}]}]}]
    
    
    console .log (
      'Removing IP-1A4 does nothing:',
      removeIdIfLast ('IP-1A4') (input)
    )
    console .log (
      'Removing IP-1A5 succeeds:',
      removeIdIfLast ('IP-1A5') (input)
    )
    .as-console-wrapper {max-height: 100% !important; top: 0}

    【讨论】:

      猜你喜欢
      • 2019-09-12
      • 1970-01-01
      • 2017-08-19
      • 2020-10-30
      • 2022-07-19
      • 2021-12-11
      • 2019-01-25
      • 1970-01-01
      相关资源
      最近更新 更多