为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕桂英4014372 2024-01-18 15:52:17
我有一些具有以下结构的 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 深度的情况下执行此操作?任何帮助将不胜感激!
查看完整描述

3 回答

?
holdtom

TA贡献1805条经验 获得超10个赞

在开始之前,我们必须首先解决输入数据的不规则形状 -


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 应用程序由您完成。


查看完整回答
反对 回复 2024-01-18
?
江户川乱折腾

TA贡献1851条经验 获得超5个赞

不完全清楚您要从问题中寻找什么,但我猜您需要修改数据以确保在渲染时正确突出显示匹配的数据?

这是使用对象扫描查找匹配对象的解决方案

// 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 camelcase

const 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 camelcase

const 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')));

// => 1

console.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')));

// => 1

console.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')));

// => 2

console.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>


使用库对你来说可能不值得,这是一种权衡。如果您对此答案有疑问/想法,请告诉我。


查看完整回答
反对 回复 2024-01-18
?
慕尼黑的夜晚无繁华

TA贡献1864条经验 获得超6个赞

正如谢谢指出的那样,不一致的数据格式使得为此编写好的代码变得更加困难。我的方法略有不同。我没有转换数据,而是为通用函数编写了一个包装器,以便以更有用的方式处理此输出。


我们从一个函数开始collect,它将递归地处理{name?, search_words?, children?, ...rest}对象,返回与给定谓词匹配的节点并在子节点上重复出现。我们用函数 来调用它,search它接受一个搜索词并从中创建一个谓词。(这里我们测试是否name或任何search_term匹配该术语;对于部分匹配、不区分大小写等,这很容易修改。)


然后我们编写我提到的包装器searchLocations。它下降到节点,然后映射并组合调用每个根值.root的结果。search


const collect = (pred) => ({children = [], ...rest}) => [

  ... (pred (rest) ? [rest] : []),

  ... children .flatMap (collect (pred))

]

  

const search = (term) => 

  collect (({name = '', search_words = []}) => name == term || search_words .includes (term))


const searchLocations = (locations, term) => 

  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) =>

  xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>

    pred (rest) || kids.length

      ? [{...rest, ...(kids.length ? {children: kids} : {})}]

      : []

  )

第二个是专门针对这个问题的:searchLocation。它调用deepFilter使用由搜索项和已讨论的转换结构构造的谓词。它使用convert结构的帮助器,以及search将搜索词转换为谓词的帮助器,该谓词在名称和所有搜索词上查找(不区分大小写)部分匹配。


const searchLocations = (loc, locations = convert(loc)) => (term) =>

  term.length ? deepFilter (search (term)) (locations) : locations

这通过用户界面来演示,该用户界面显示嵌套中的位置<UL>,并带有实时过滤位置的搜索框。


例如,如果您在搜索框中输入“w”,您将得到


Europe

  England

    London (city, capital)

      Westminster (borough)

North America

  Canada

    Ottawa

因为“Westminster”和“Ottawa”是唯一的匹配项。


如果你输入“城市”你会得到


Europe

  England

    London (city, capital)

    Manchester (city)

  France

    Paris (city, capital)

您可以在此代码片段中看到它的实际效果:


// utility function

const deepFilter = (pred) => (xs) =>

  xs .flatMap (({children = [], ...rest}, _, __, kids = deepFilter (pred) (children)) =>

    pred (rest) || kids.length

      ? [{...rest, ...(kids.length ? {children: kids} : {})}]

      : []

  )


// helper functions

const search = (t = '', term = t.toLowerCase()) => ({name = '', search_words = []}) =>

  term.length &&  (

    name .toLowerCase () .includes (term) ||

    search_words .some (word => word .toLowerCase() .includes (term))

  )


const convert = ({root}) => 

  Object.entries (root) .map (([name, v]) => ({name, ...v}))



// main function

const searchLocations = (loc, locations = convert(loc)) => (term) =>

  term.length ? deepFilter (search (term)) (locations) : locations



// sample data

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' }] } } };



// main function specialized to given data

const mySearch = searchLocations(myData)



// UI demo

const format = (locations, searchTerm) => `<ul>${

  locations.map(loc => `<li>${

    loc.name + 

    (loc.search_words ? ` (${loc.search_words. join(', ')})` : ``) + 

    (loc.children ? format(loc.children, searchTerm) : '')

  }</li>`)

  .join('') 

}</ul>`


const render = (locations, searchTerm) => 

  document .getElementById ('main') .innerHTML = format (locations, searchTerm)


document .getElementById ('search') .addEventListener (

  'keyup',

  (e) => render (mySearch (e.target.value))

)


// show demo

render (mySearch (''))

<div style="float: right" id="control">

  <label>Search: <input type="text" id="search"/></label>

</div>

<div style="margin-top: -1em" id="main"></div>


显然,这并没有使用Vue来生成树,只是进行一些字符串操作和innerHTML. 我把这部分留给你。但它应该显示另一种过滤嵌套结构的方法。


查看完整回答
反对 回复 2024-01-18
  • 3 回答
  • 0 关注
  • 152 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信