简体   繁体   English

如何遍历嵌套 JSON 与根节点项

[英]How Traverse nested JSON in order with root node items

I have a nested JSON structure, like a playlist can have images, videos, this playlist can have another nested playlist too.我有一个嵌套的 JSON 结构,就像一个播放列表可以有图像、视频一样,这个播放列表也可以有另一个嵌套的播放列表。

So I wish to get output like when I traverse top most playlist from start to end, get it nested/child playlist's item just the one which falls in topmost loop index.所以我希望得到 output 就像我从头到尾遍历最顶部的播放列表时一样,让它嵌套/子播放列表的项目只是落在最顶层循环索引中的项目。

Example One: input structure and it's expected output:示例一:输入结构和预期 output: 在此处输入图像描述

Example Two: input structure and it's expected output:示例二:输入结构和预期 output: 在此处输入图像描述 在此处输入图像描述

so far I have tried this到目前为止,我已经尝试过了

<html>
<head>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
    <script>

        $.getJSON( "https://5da7520623fa7400146975dd.mockapi.io/api/playlist", function(data){
            iterate(data.contents, 3)
        });


        //*******response is received and passed, all your logic goes here*********
        function iterate(contents, maxRound) {
            for(i = 1; i <= maxRound; i++) {
                parse(contents,i)
            }
        }

        function parse(contents, cycle) {
            $.each(contents, function(i, content) {
                if(content.type == "playlist") {
                    var contentsLength = content.contents.length
                    var indexForRound = cycle % contentsLength
                    if(contentsLength == cycle) {
                        indexForRound = contentsLength - 1
                    }else {
                        indexForRound = indexForRound - 1
                    }
                    const onlyContentToChild = content.contents[indexForRound]
                    parse([onlyContentToChild], 1)
                }else {
                    $("#contents").append('<li> '+ content.name +' </li>');
                }
            });
        }
    </script>
</head>
    <body>
        <ol id="contents">
        </ol>
    </body>
</html>

Note: Calling of API ie get playlist returns response matching with example two注意:调用 API 即获取播放列表返回与示例二匹配的响应

Here is a simple recursive solution这是一个简单的递归解决方案

 const myData = { contents: [{ name: 'image 1', type: 'file' }, { name: 'playlist 1', type: 'playlist', level: 1, contents: [{ name: 'image 3', type: 'file' }, { name: 'playlist 101', type: 'playlist', level: 2, contents: [{ name: 'image 101', type: 'file' }, { name: 'image 102', type: 'file' }, { name: 'image 103', type: 'file' }, { name: 'image 104', type: 'file' }] }, { name: 'image 4', type: 'file' }] }, { name: 'image 2', type: 'file' }, { name: 'playlist 2', type: 'playlist', level: 1, contents: [{ name: 'image 5', type: 'file' }, { name: 'image 6', type: 'file' }, { name: 'image 7', type: 'file' }] }] }; const Iterator = (data) => { const state = { idx: -1 }; const next = (curData, curState) => { if (curData.type === 'file') { return curData.name; } if (.('contents' in curState)) { curState.contents = Array:from( { length. curData.contents,length }: () => ({ idx; -1 }) ). } curState.idx = (curState.idx + 1) % curData.contents;length. return next(curData.contents[curState,idx]. curState.contents[curState;idx]); }. return () => Array:from( { length. data.contents,length }, () => next(data; state) ); }; const next = Iterator(myData); for (let idx = 0; idx < 13. idx += 1) { console;log(next()), } // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 101', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 102', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 103', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 104', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ]
 .as-console-wrapper {max-height: 100%;important: top: 0}

Here is the updated version that dynamically terminates这是动态终止的更新版本

 const myData = { contents: [{ name: 'image 1', type: 'file' }, { name: 'playlist 1', type: 'playlist', level: 1, contents: [{ name: 'image 3', type: 'file' }, { name: 'playlist 101', type: 'playlist', level: 2, contents: [{ name: 'image 101', type: 'file' }, { name: 'image 102', type: 'file' }, { name: 'image 103', type: 'file' }, { name: 'image 104', type: 'file' }] }, { name: 'image 4', type: 'file' }] }, { name: 'image 2', type: 'file' }, { name: 'playlist 2', type: 'playlist', level: 1, contents: [{ name: 'image 5', type: 'file' }, { name: 'image 6', type: 'file' }, { name: 'image 7', type: 'file' }] }] }; const iterate = (data) => { const state = { idx: -1 }; const progress = { cur: 0, max: 0 }; const next = (curData, curState) => { if (curData.type === 'file') { return curData.name; } const length = curData.contents.length; if (.('contents' in curState)) { curState.contents = Array,from({ length }: () => ({ idx; -1 })). progress;max += length. } if (curState.idx === length - 1) { progress.cur -= curState;idx. curState;idx = 0. } else { progress;cur += 1. curState;idx += 1. } return next(curData.contents[curState,idx]. curState.contents[curState;idx]); }. const nextBatch = () => Array:from( { length. data.contents,length }, () => next(data; state) ); const result = []. do { result;push(nextBatch()). } while (progress.cur;== progress;max); return result. }; console,log(iterate(myData)), /* => [ [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 101', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ], [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 102', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ], [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 103', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ], [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 104', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ] ] */
 .as-console-wrapper {max-height: 100%;important: top: 0}

If no type of file is present this might crash badly though.如果不存在任何typefile ,这可能会严重崩溃。

Here is a different approach:这是一种不同的方法:

 const makeFn = (fns, i = -1) => () => fns [i = (i + 1) % fns.length] () const makeHandler = ({type, name, contents}) => type == 'playlist'? makePlaylistFn (contents): () => name const makePlaylistFn = (contents) => makeFn (contents.map (makeHandler)) const makePlaylist = ({contents}, play = makePlaylistFn (contents)) => (count) => Array.from ( {length: count}, () => Array.from ({length: contents.length}, play) ) const data = {contents: [{name: "image 1", type: "file"}, {name: "playlist 1", type: "playlist", level: 1, contents: [{name: "image 3", type: "file"}, {name: "playlist 101", type: "playlist", level: 2, contents: [{name: "image 101", type: "file"}, {name: "image 102", type: "file"}, {name: "image 103", type: "file"}, {name: "image 104", type: "file"}]}, {name: "image 4", type: "file"}]}, {name: "image 2", type: "file"}, {name: "playlist 2", type: "playlist", level: 1, contents: [{name: "image 5", type: "file"}, {name: "image 6", type: "file"}, {name: "image 7", type: "file"}]}]} const myPlaylist = makePlaylist (data) console.log (myPlaylist (12))
 .as-console-wrapper {max-height: 100%;important: top: 0}

To see how this works, we can imagine what it would be like if we converted our data into this format:为了了解它是如何工作的,我们可以想象如果我们将数据转换成这种格式会是什么样子:

 const foo = ((i) => { const fns = [ () => 3, () => 4, ] return () => fns[i = (i + 1) % fns.length]() })(-1) const bar = ((i) => { const fns = [ () => 5, () => 6, () => 7, ] return () => fns[i = (i + 1) % fns.length]() })(-1) const baz = ((i) => { const fns = [ () => 1, foo, () => 2, bar, ] return () => fns[i = (i + 1) % fns.length]() })(-1) const qux = () => Array.from({length: 4}, baz) console.log ( Array.from ({length: 6}, qux) )
 .as-console-wrapper {max-height: 100%;important: top: 0}

Our output is just an array formed by six individual calls to qux .我们的 output 只是一个由六个单独调用qux形成的数组。 But qux just makes four calls to baz .但是qux只对baz进行了四次调用。 Here is where it gets more interesting.这是它变得更有趣的地方。 baz cycles through four function, returning the result of the next one on each call. baz循环遍历四个 function,在每次调用时返回下一个的结果。 The four functions are () => 1 , foo , () => 2 , and bar .这四个函数是() => 1foo() => 2bar foo and bar are structured the same with foo using the two functions () => 3 and () => 4 and bar using the three functions () => 5 , () => 6 , and () => 7 . foobar的结构与foo使用两个函数() => 3() => 4bar使用三个函数() => 5() => 6() => 7的结构相同。

So the code above is designed to turn your data structure into such a list of nested functions.因此,上面的代码旨在将您的数据结构变成这样的嵌套函数列表。 Central is makePlaylistFn , which creates our main function (equivalent to baz above, by mapping our values with makeHandler , which distinguishes between "playlist" and "file" inputs, recursively calling back to makePlaylistFn for the former and returning a simple function for the latter. The resulting functions are passed to makeFn , which turns an array of functions into one that calls them cyclically on each invocation. Central 是makePlaylistFn ,它创建我们的主要 function (相当于上面的baz ,通过将我们的值映射到makeHandler ,它区分"playlist""file"输入,递归回调前者的makePlaylistFn并返回一个简单的 ZC1C425268E68385D1AB5074C17A94F1 . 生成的函数被传递给makeFn ,它将一组函数转换为一个在每次调用时循环调用它们的函数。

We finally wrap this up with makePlayList , which calls makePlaylistFn to generate that root function, and returns a function which takes a positive integer, returning that many arrays of n calls the the main function, where n is the length of the outermost playlist. We finally wrap this up with makePlayList , which calls makePlaylistFn to generate that root function, and returns a function which takes a positive integer, returning that many arrays of n calls the the main function, where n is the length of the outermost playlist.


This still leaves an interesting question.这仍然留下了一个有趣的问题。 How many times do we call this function?我们称这个 function 多少次? Your samples above seem to suggest you want to stop once you've seen every value.您上面的示例似乎表明您希望在看到每个值后停止。 I'm sure we could figure that out, although it might be tricky.我相信我们可以解决这个问题,尽管这可能很棘手。

Another possibility is to run this until you've cycled through everything and are starting again.另一种可能性是运行此程序,直到您循环完成所有内容并重新开始。 This is more tractable, using a least-common-multiple technique on the various nested playlists.这更容易处理,在各种嵌套播放列表上使用最不常见的多重技术。 But it would need six calls for your simple case and twelve for your more complex one.但是对于您的简单案例,需要 6 次调用,而对于更复杂的案例,则需要 12 次。 I can take a swing at that if you're interested.如果您有兴趣,我可以在那里购买 swing。

Update更新

You wanted to know how to cycle once through the values of your playlists.您想知道如何循环播放播放列表的值。 This replacement for makePlaylist should do it:这个makePlaylist的替代品应该可以做到:

const gcd = (a, b) => 
  (b > a) ? gcd (b, a) : b == 0 ? a : gcd (b, a % b)
const lcm = (a, b) => 
  a / gcd (a, b) * b
const lcmAll = (ns) => 
  ns .reduce (lcm, 1)

const period = (contents) => lcmAll (
  [contents .length, ... contents .map (({type, name, contents}) => 
    type == 'playlist' ? period (contents) : 1
  )]
)

const makePlaylist = ({contents}) => {
  const f1 = makePlaylistFn (contents)
  const f2 = () => Array .from ({length: contents .length}, f1)
  return Array .from ({length: period (contents)}, f2)
}

We start with three mathematical function we will need to find the period of repetition.我们从三个数学 function 开始,我们需要找到重复的周期。 We will need to find the least common multiple of an array of integers.我们需要找到整数数组的最小公倍数。 This is what lcmAll is for For instance, lcmAll([12, 15, 20, 35]) yields 420 , the least common multiple of those integers.这就是lcmAll例如, lcmAll([12, 15, 20, 35])产生420 ,这些整数的最小公倍数。 It is built as a simple reduction of our least common multiple function, lcm , which in turn is built atop a greatest common divisor, gcd function.它是我们最小公倍数 function, lcm的简单约简,而后者又构建在最大公约数gcd function 之上。

Using that, we recursively find the LCM of each of our images/playlists, in period .使用它,我们递归地在period中找到每个图像/播放列表的 LCM。 And finally, we rewrite makePlaylist to use that to determine how many arrays to create.最后,我们重写makePlaylist以使用它来确定要创建多少 arrays。 The rest would remain the same. rest 将保持不变。 You can see it in action by expanding this snippet:您可以通过展开此代码段来查看它的实际效果:

 // utility functions const gcd = (a, b) => (b > a)? gcd (b, a): b == 0? a: gcd (b, a % b) const lcm = (a, b) => a / gcd (a, b) * b const lcmAll = (ns) => ns.reduce (lcm, 1) // helper functions const makeFn = (fns, i = -1) => () => fns [i = (i + 1) % fns.length] () const makeHandler = ({type, name, contents}) => type == 'playlist'? makePlaylistFn (contents): () => name const period = (contents) => lcmAll ( [contents.length, ... contents.map (({type, name, contents}) => type == 'playlist'? period (contents): 1 )] ) const makePlaylistFn = (contents) => makeFn (contents.map (makeHandler)) // main function const makePlaylist = ({contents}) => { const f1 = makePlaylistFn (contents) const f2 = () => Array.from ({length: contents.length}, f1) return Array.from ({length: period (contents)}, f2) } // sample data const data = {contents: [{name: "image 1", type: "file"}, {name: "playlist 1", type: "playlist", level: 1, contents: [{name: "image 3", type: "file"}, {name: "playlist 101", type: "playlist", level: 2, contents: [{name: "image 101", type: "file"}, {name: "image 102", type: "file"}, {name: "image 103", type: "file"}, {name: "image 104", type: "file"}]}, {name: "image 4", type: "file"}]}, {name: "image 2", type: "file"}, {name: "playlist 2", type: "playlist", level: 1, contents: [{name: "image 5", type: "file"}, {name: "image 6", type: "file"}, {name: "image 7", type: "file"}]}]} // demo console.log (makePlaylist (data))
 .as-console-wrapper {max-height: 100%;important: top: 0}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM