如何使用 vue/javascript 通过多个属性过滤深度嵌套的 json

我有一些具有以下结构的 JSON:


{

  "root": {

    "Europe": {

      "children": [

        {

          "name": "Germany"

        },

        {

          "name": "England",

          "children": [

            {

              "name": "London",

              "search_words": ["city", "capital"],

              "children": [

                {

                  "name": "Westminster",

                  "search_words": ["borough"]

                }

              ]

            },

            {

              "name": "Manchester",

              "search_words": ["city"]

            }

          ]

        },

        {

          "name": "France",

          "children": [

            {

              "name": "Paris",

              "search_words": ["city", "capital"]

            }

          ]

        }

      ]

    },

    "North America": {

      "children": [

        {

          "name": "Canada",

          "children": [

            {

              "name": "Toronto"

            },

            {

              "name": "Ottawa"

            }

          ]

        },

        {

          "name": "United States"

        }

      ]

    }

  }

}

我想根据文本搜索过滤 JSON。我应该能够通过 thename和 any进行搜索search_words。最后一个问题是 JSON 可以是任意深度,因此它需要能够搜索所有级别。


我还需要循环遍历并打印出 HTML 中的 JSON(使用 Vue),并根据搜索进行更新。目前还不清楚如何在不知道 JSON 深度的情况下执行此操作?


任何帮助将不胜感激!


慕桂英4014372
浏览 166回答 3
3回答

holdtom

在开始之前,我们必须首先解决输入数据的不规则形状 -const data2 =  { name:"root"  , children:      Array.from        ( Object.entries(data.root)        , ([ country, _ ]) =>            Object.assign({ name:country }, _)        )  }console.log(JSON.stringify(data2, null, 2))现在我们看到的data2是一个统一的{ name, children: [ ... ]}形状——{  "name": "root",  "children": [    {      "name": "Europe",      "children": [        { "name": "Germany" },        {          "name": "England",          "children": [            {              "name": "London",              "search_words": [ "city", "capital" ],              "children": [                {                  "name": "Westminster",                  "search_words": [ "borough" ]                }              ]            },            {              "name": "Manchester",              "search_words": [ "city" ]            }          ]        },        {          "name": "France",          "children": [            {              "name": "Paris",              "search_words": [ "city", "capital" ]            }          ]        }      ]    },    {      "name": "North America",      "children": [        {          "name": "Canada",          "children": [            { "name": "Toronto" },            { "name": "Ottawa" }          ]        },        { "name": "United States" }      ]    }  ]}现在我们编写一个通用的深度优先遍历函数,dft-function* dft (t, path = []){ for (const _ of t.children ?? [])    yield* dft(_, [...path, t.name ])  yield [path, t]}我们的dft函数为我们的输入树中的path每个元素提供了一个-et["root","Europe"]{"name":"Germany"}["root","Europe","England","London"]{name:"Westminster", search_words:["borough"]}["root","Europe","England"]{name:"London", search_words:["city","capital"], children:[...]}["root","Europe","England"]{name:"Manchester", search_words:["city"]}["root","Europe"]{name:"England", children:[...]}["root","Europe","France"]{name:"Paris", search_words:["city","capital"]}["root","Europe"]{name:"France", children:[...]}["root"]{name:"Europe", children:[...]}["root","North America","Canada"]{name:"Toronto"}现在我们知道了每个节点的路径,我们可以创建一个index使用path和 anysearch_words链接回该节点的路径 -const index = t =>  Array.from    ( dft(t)    , ([path, e]) =>        [ [...path, e.name, ...e.search_words ?? [] ] // all words to link to e        , e                                           // e        ]    )    .reduce      ( (m, [ words, e ]) =>          insertAll(m, words, e) // update the index using generic helper      , new Map      )这取决于通用助手insertAll-const insertAll = (m, keys, value) =>  keys.reduce    ( (m, k) =>        m.set(k, [ ...m.get(k) ?? [], value ])    , m    )完成后index,我们有一种方法可以为任何搜索词创建快速查找 -const myIndex =   index(data2)console.log(myIndex)Map { "Europe" =>    [{"name":"Germany"},{"name":"Westminster",...},{"name":"London",...},{"name":"Manchester",...},{"name":"England"...},{"name":"Manchester",...}]},{"name":"Paris",...},{"name":"France"...},{"name":"Europe"...},{"name":"Manchester",...}]},{"name":"France"...}]}], "Germany" =>     [{"name":"Germany"}], "England" =>    [{"name":"Westminster",...},{"name":"London",...},{"name":"Manchester",...},{"name":"England"...},{"name":"Manchester",...}]}], "London" =>    [{"name":"Westminster",...},{"name":"London",...}], "Westminster" =>    [{"name":"Westminster",...}], "borough" =>    [{"name":"Westminster",...}], "city" =>    [{"name":"London",...},{"name":"Manchester",...},{"name":"Paris",...}], "capital" =>    [{"name":"London",...},{"name":"Paris",...}], "Manchester" =>    [{"name":"Manchester",...}], "France" =>    [{"name":"Paris",...},{"name":"France"...}], "Paris" =>    [{"name":"Paris",...}], "North America" =>    [{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...},{"name":"United States"},{"name":"North America"...},    {"name":"United States"}]}], "Canada" =>    [{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...}], "Toronto" =>    [{"name":"Toronto"}], "Ottawa" =>    [{"name":"Ottawa"}], "United States" =>    [{"name":"United States"}]   }这应该突出显示数据中剩余的不一致之处。例如,您有一些嵌套在city、capital或下的节点borough。另外值得注意的是,我们可能应该使用s.toLowerCase()所有索引键,以便查找不区分大小写。这是留给读者的练习。创建index很容易,您只需要做一次-const myIndex =   index(data2)您的索引可以根据需要重复用于任意多次查找 -console.log(myIndex.get("Toronto") ?? [])console.log(myIndex.get("France") ?? [])console.log(myIndex.get("Paris") ?? [])console.log(myIndex.get("Canada") ?? [])console.log(myIndex.get("Zorp") ?? [])[{"name":"Toronto"}][{"name":"Paris",...},{"name":"France"...}][{"name":"Paris",...}][{"name":"Toronto"},{"name":"Ottawa"},{"name":"Canada"...}][]将结果插入 Vue 应用程序由您完成。

江户川乱折腾

不完全清楚您要从问题中寻找什么,但我猜您需要修改数据以确保在渲染时正确突出显示匹配的数据?这是使用对象扫描查找匹配对象的解决方案// const objectScan = require('object-scan');const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };// eslint-disable-next-line camelcaseconst mySearchFn = (term) => ({ name, search_words = [] }) => name === term || search_words.includes(term);const search = (input, searchFn) => objectScan(['**[*]'], {  filterFn: ({ value, context }) => {    if (searchFn(value)) {      const { children, ...match } = value;      context.push(match);    }  }})(input, []);console.log(search(myData, mySearchFn('Toronto')));// => [ { name: 'Toronto' } ]console.log(search(myData, mySearchFn('borough')));// => [ { name: 'Westminster', search_words: [ 'borough' ] } ]console.log(search(myData, mySearchFn('capital')));// => [ { name: 'Paris', search_words: [ 'city', 'capital' ] }, { name: 'London', search_words: [ 'city', 'capital' ] } ].as-console-wrapper {max-height: 100% !important; top: 0}<script src="https://bundle.run/object-scan@13.8.0"></script>这就是您如何注入信息,然后渲染管道可以获取这些信息// const objectScan = require('object-scan');const myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };// eslint-disable-next-line camelcaseconst mySearchFn = (term) => ({ name, search_words = [] }) => name === term || search_words.includes(term);const search = (input, searchFn) => objectScan(['**[*]'], {  filterFn: ({ value }) => {    if (searchFn(value)) {      value.css = { highlight: true };      return true;    } else {      delete value.css;      return false;    }  },  rtn: 'count' // return number of matches})(input);console.log(search(myData, mySearchFn('Toronto')));// => 1console.log(myData);// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ] } ] }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ] } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto', css: { highlight: true } }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }console.log(search(myData, mySearchFn('borough')));// => 1console.log(myData);// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ], css: { highlight: true } } ] }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ] } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto' }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }console.log(search(myData, mySearchFn('capital')));// => 2console.log(myData);// => { root: { Europe: { children: [ { name: 'Germany' }, { name: 'England', children: [ { name: 'London', search_words: [ 'city', 'capital' ], children: [ { name: 'Westminster', search_words: [ 'borough' ] } ], css: { highlight: true } }, { name: 'Manchester', search_words: [ 'city' ] } ] }, { name: 'France', children: [ { name: 'Paris', search_words: [ 'city', 'capital' ], css: { highlight: true } } ] } ] }, 'North America': { children: [ { name: 'Canada', children: [ { name: 'Toronto' }, { name: 'Ottawa' } ] }, { name: 'United States' } ] } } }.as-console-wrapper {max-height: 100% !important; top: 0}<script src="https://bundle.run/object-scan@13.8.0"></script>使用库对你来说可能不值得,这是一种权衡。如果您对此答案有疑问/想法,请告诉我。

慕尼黑的夜晚无繁华

正如谢谢指出的那样,不一致的数据格式使得为此编写好的代码变得更加困难。我的方法略有不同。我没有转换数据,而是为通用函数编写了一个包装器,以便以更有用的方式处理此输出。我们从一个函数开始collect,它将递归地处理{name?, search_words?, children?, ...rest}对象,返回与给定谓词匹配的节点并在子节点上重复出现。我们用函数 来调用它,search它接受一个搜索词并从中创建一个谓词。(这里我们测试是否name或任何search_term匹配该术语;对于部分匹配、不区分大小写等,这很容易修改。)然后我们编写我提到的包装器searchLocations。它下降到节点,然后映射并组合调用每个根值.root的结果。searchconst collect = (pred) => ({children = [], ...rest}) => [&nbsp; ... (pred (rest) ? [rest] : []),&nbsp; ... children .flatMap (collect (pred))]&nbsp;&nbsp;const search = (term) =>&nbsp;&nbsp; collect (({name = '', search_words = []}) => name == term || search_words .includes (term))const searchLocations = (locations, term) =>&nbsp;&nbsp; Object.values (locations .root) .flatMap (search (term))const locations = {root: {Europe: {children: [{name: "Germany"}, {name: "England", children: [{name: "London", search_words: ["city", "capital"], children: [{name: "Westminster", search_words: ["borough"]}]}, {name: "Manchester", search_words: ["city"]}]}, {name: "France", children: [{name: "Paris", search_words: ["city", "capital"]}]}]}, "North America": {children: [{name: "Canada", children: [{name: "Toronto"}, {name: "Ottawa"}]}, {name: "United States"}]}}}console .log ('Toronto', searchLocations (locations, 'Toronto'))console .log ('borough', searchLocations (locations, 'borough'))console .log ('capital', searchLocations (locations, 'capital')).as-console-wrapper {max-height: 100% !important; top: 0}如果您想要的(听起来像是您可能想要的)与输入的结构相同,仅保留包含匹配项所需的节点,那么我们应该能够从树过滤函数开始执行类似的操作。假期后我会尝试看看。更新我确实又看了一遍,希望将树作为一棵树来过滤。代码并没有那么难。但这一次,我确实使用了一个convert函数将您的数据转换为更一致的递归结构。因此,整个对象变成一个数组,根部有两个元素,一个元素为name“欧洲”,另一个元素为name“北美”,每个元素都有其现有children节点。这使得所有进一步的处理变得更加容易。这里有两个关键函数:第一个是通用deepFilter函数,它采用谓词和项目数组,这些项目的节点children结构可能类似于其父级,并返回一个新版本,其中包含与谓词及其完整祖先匹配的任何内容。它看起来像这样:const deepFilter = (pred) => (xs) =>&nbsp; xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>&nbsp; &nbsp; pred (rest) || kids.length&nbsp; &nbsp; &nbsp; ? [{...rest, ...(kids.length ? {children: kids} : {})}]&nbsp; &nbsp; &nbsp; : []&nbsp; )第二个是专门针对这个问题的:searchLocation。它调用deepFilter使用由搜索项和已讨论的转换结构构造的谓词。它使用convert结构的帮助器,以及search将搜索词转换为谓词的帮助器,该谓词在名称和所有搜索词上查找(不区分大小写)部分匹配。const searchLocations = (loc, locations = convert(loc)) => (term) =>&nbsp; term.length ? deepFilter (search (term)) (locations) : locations这通过用户界面来演示,该用户界面显示嵌套中的位置<UL>,并带有实时过滤位置的搜索框。例如,如果您在搜索框中输入“w”,您将得到Europe&nbsp; England&nbsp; &nbsp; London (city, capital)&nbsp; &nbsp; &nbsp; Westminster (borough)North America&nbsp; Canada&nbsp; &nbsp; Ottawa因为“Westminster”和“Ottawa”是唯一的匹配项。如果你输入“城市”你会得到Europe&nbsp; England&nbsp; &nbsp; London (city, capital)&nbsp; &nbsp; Manchester (city)&nbsp; France&nbsp; &nbsp; Paris (city, capital)您可以在此代码片段中看到它的实际效果:// utility functionconst deepFilter = (pred) => (xs) =>&nbsp; xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>&nbsp; &nbsp; pred (rest) || kids.length&nbsp; &nbsp; &nbsp; ? [{...rest, ...(kids.length ? {children: kids} : {})}]&nbsp; &nbsp; &nbsp; : []&nbsp; )// helper functionsconst search = (t = '', term = t.toLowerCase()) => ({name = '', search_words = []}) =>&nbsp; term.length &&&nbsp; (&nbsp; &nbsp; name .toLowerCase () .includes (term) ||&nbsp; &nbsp; search_words .some (word => word .toLowerCase() .includes (term))&nbsp; )const convert = ({root}) =>&nbsp;&nbsp; Object.entries (root) .map (([name, v]) => ({name, ...v}))// main functionconst searchLocations = (loc, locations = convert(loc)) => (term) =>&nbsp; term.length ? deepFilter (search (term)) (locations) : locations// sample dataconst myData = { root: { Europe: { children: [{ name: 'Germany' }, { name: 'England', children: [{ name: 'London', search_words: ['city', 'capital'], children: [{ name: 'Westminster', search_words: ['borough'] }] }, { name: 'Manchester', search_words: ['city'] }] }, { name: 'France', children: [{ name: 'Paris', search_words: ['city', 'capital'] }] }] }, 'North America': { children: [{ name: 'Canada', children: [{ name: 'Toronto' }, { name: 'Ottawa' }] }, { name: 'United States' }] } } };// main function specialized to given dataconst mySearch = searchLocations(myData)// UI democonst format = (locations, searchTerm) => `<ul>${&nbsp; locations.map(loc => `<li>${&nbsp; &nbsp; loc.name +&nbsp;&nbsp; &nbsp; (loc.search_words ? ` (${loc.search_words. join(', ')})` : ``) +&nbsp;&nbsp; &nbsp; (loc.children ? format(loc.children, searchTerm) : '')&nbsp; }</li>`)&nbsp; .join('')&nbsp;}</ul>`const render = (locations, searchTerm) =>&nbsp;&nbsp; document .getElementById ('main') .innerHTML = format (locations, searchTerm)document .getElementById ('search') .addEventListener (&nbsp; 'keyup',&nbsp; (e) => render (mySearch (e.target.value)))// show demorender (mySearch (''))<div style="float: right" id="control">&nbsp; <label>Search: <input type="text" id="search"/></label></div><div style="margin-top: -1em" id="main"></div>显然,这并没有使用Vue来生成树,只是进行一些字符串操作和innerHTML. 我把这部分留给你。但它应该显示另一种过滤嵌套结构的方法。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript