简体   繁体   中英

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.

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.

Example One: input structure and it's expected output: 在此处输入图像描述

Example Two: input structure and it's expected 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

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.

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 . But qux just makes four calls to baz . Here is where it gets more interesting. baz cycles through four function, returning the result of the next one on each call. The four functions are () => 1 , foo , () => 2 , and bar . 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 .

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.

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? 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. I can take a swing at that if you're interested.

Update

You wanted to know how to cycle once through the values of your playlists. This replacement for makePlaylist should do it:

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

Using that, we recursively find the LCM of each of our images/playlists, in period . And finally, we rewrite makePlaylist to use that to determine how many arrays to create. The rest would remain the same. 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}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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