I'm trying to find all the objects that has the type equal to question
in this deep nested array:
{
"uuid": "707a5ffd-68e2-4dbd-b539-128512ba3a0a",
"type": "page",
"items": [
{
"uuid": "9d823429-cc24-444d-a21c-a81357305851",
"title": "1",
"type": "question",
},
{
"type": "section",
"title": "2",
"uuid": "346dec94-124c-4932-bd40-af9dc68f1d27",
"items": [
{
"uuid": "bf0a9ab9-99cc-4833-b3d3-84a97072e85f",
"title": "2.1",
"type": "question",
}
],
},
{
"type": "section",
"title": "3",
"uuid": "4964096d-0de9-4ab1-ace5-e42516d6b866",
"items": [
{
"uuid": "b2170580-1e2e-4fb4-a7b9-a56b79db21b3",
"title": "3.1",
"type": "question",
}
],
}
],
"params": {
"collapsed": false
}
}
So far I have the following algorithm to transverse the deep array:
export const findQuestions = (items, item = undefined, questions = []) => {
const _questions = [...questions];
if (
item !== undefined
&& !Object.prototype.hasOwnProperty.call(item, 'items')
) {
return _questions;
}
for (const _item of items) {
if (_item.type === 'question') {
_questions.push(_item);
}
if (Object.prototype.hasOwnProperty.call(_item, 'items')) {
return findQuestions(_item.items, _item, _questions);
}
}
return _questions;
};
I call findQuestions
by passing the object referenced above:
const questions = findQuestions([this.lastPage]);
Unfortunately the algorithm stops at 2.1
and do not transverse up to 3.1
. My objective is to have an array with all questions
:
[
{
"title": "1",
},
{
"title": "2.1",
},
{
"title": "3.1",
}
]
I'm assuming that the problem is at
if (
item !== undefined
&& !Object.prototype.hasOwnProperty.call(item, 'items')
) {
return _questions;
}
which returns sooner than it should. How can I fetch all questions
?
CodePudding user response:
Another approach builds this atop reusable functions that you might keep around for other needs. Here we build getQuestionTitles
as a simple call to findByType ('question')
and an extraction of the titles from the matching nodes. findByType
is a call to deepFilter
using the predicate (o) => o .type == 'question'
supplied by findByType
. And deepFilter
calls traverse
to visit all the nodes and recursive subnodes in an Object (in depth-first pre-order; post-order would be a trivial change.)
function * traverse (o) {
yield o;
if (Object (o) === o) {
for (let c of Object .values (o)) {yield * traverse (c)}
}
}
const deepFilter = (pred) => (o) =>
[... traverse (o)] .filter (pred)
const findByType = (target) =>
deepFilter ((o) => o .type == target)
const getQuestionTitles = (obj) =>
findByType ('question') (obj) .map (({title}) => ({title}))
const data = {uuid: "707a5ffd-68e2-4dbd-b539-128512ba3a0a", type: "page", items: [{uuid: "9d823429-cc24-444d-a21c-a81357305851", title: "1", type: "question"}, {type: "section", title: "2", uuid: "346dec94-124c-4932-bd40-af9dc68f1d27", items: [{uuid: "bf0a9ab9-99cc-4833-b3d3-84a97072e85f", title: "2.1", type: "question"}]}, {type: "section", title: "3", uuid: "4964096d-0de9-4ab1-ace5-e42516d6b866", items: [{uuid: "b2170580-1e2e-4fb4-a7b9-a56b79db21b3", title: "3.1", type: "question"}]}], params: {collapsed: false}}
console .log (getQuestionTitles (data))
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
While getQuestionTitles
is specific to this problem, the others are increasingly more generic. traverse
is built as a generator since we could use it for find
as well as filter
, and we'd want to stop as soon as it was done.
I find this a very useful style of coding: keeping around generic abstractions and writing my specific code on them, and in that way keeping the custom code extremely simple.
CodePudding user response:
Here is a simple recursive function
const data = {
"uuid": "707a5ffd-68e2-4dbd-b539-128512ba3a0a",
"type": "page",
"items": [
{
"uuid": "9d823429-cc24-444d-a21c-a81357305851",
"title": "1",
"type": "question",
},
{
"type": "section",
"title": "2",
"uuid": "346dec94-124c-4932-bd40-af9dc68f1d27",
"items": [
{
"uuid": "bf0a9ab9-99cc-4833-b3d3-84a97072e85f",
"title": "2.1",
"type": "question",
}
],
},
{
"type": "section",
"title": "3",
"uuid": "4964096d-0de9-4ab1-ace5-e42516d6b866",
"items": [
{
"uuid": "b2170580-1e2e-4fb4-a7b9-a56b79db21b3",
"title": "3.1",
"type": "question",
}
],
}
],
"params": {
"collapsed": false
}
}
const getQuestions = (data) => {
let questions = []
data.items.map(d => {
if(d.type == 'question') {
questions.push({type: d.type, uutd: d.uuid, title: d.title})
}
if(d.items) {
questions = [...questions, ...getQuestions(d)]
}
})
return questions;
}
console.log(getQuestions(data))
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
You can use a recursive function to return questions and recursively iterate over sections:
function getQuestions(items) {
return items.flatMap(({ type, title, items }) => type === 'question' ? { title } : getQuestions(items));
}
Example:
const data = {
"uuid": "707a5ffd-68e2-4dbd-b539-128512ba3a0a",
"type": "page",
"items": [
{
"uuid": "9d823429-cc24-444d-a21c-a81357305851",
"title": "1",
"type": "question",
},
{
"type": "section",
"title": "2",
"uuid": "346dec94-124c-4932-bd40-af9dc68f1d27",
"items": [
{
"uuid": "bf0a9ab9-99cc-4833-b3d3-84a97072e85f",
"title": "2.1",
"type": "question",
}
],
},
{
"type": "section",
"title": "3",
"uuid": "4964096d-0de9-4ab1-ace5-e42516d6b866",
"items": [
{
"uuid": "b2170580-1e2e-4fb4-a7b9-a56b79db21b3",
"title": "3.1",
"type": "question",
}
],
}
],
"params": {
"collapsed": false
}
};
function getQuestions(items) {
return items.flatMap(({ type, title, items }) => type === 'question' ? { title } : getQuestions(items));
}
console.log(getQuestions(data.items));
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
Recursive solution:
You can get all object with type
as question
as:
function getItems(obj) {
if (obj.type === "question") result.push({ title: obj.title });
if (obj.items) obj.items.forEach((o) => getItems(o));
}
const obj = {
uuid: "707a5ffd-68e2-4dbd-b539-128512ba3a0a",
type: "page",
items: [
{
uuid: "9d823429-cc24-444d-a21c-a81357305851",
title: "1",
type: "question",
},
{
type: "section",
title: "2",
uuid: "346dec94-124c-4932-bd40-af9dc68f1d27",
items: [
{
uuid: "bf0a9ab9-99cc-4833-b3d3-84a97072e85f",
title: "2.1",
type: "question",
},
],
},
{
type: "section",
title: "3",
uuid: "4964096d-0de9-4ab1-ace5-e42516d6b866",
items: [
{
uuid: "b2170580-1e2e-4fb4-a7b9-a56b79db21b3",
title: "3.1",
type: "question",
},
],
},
],
params: {
collapsed: false,
},
};
const result = [];
function getItems(obj) {
if (obj.type === "question") result.push({ title: obj.title });
if (obj.items) obj.items.forEach((o) => getItems(o));
}
getItems(obj);
console.log(result);
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
I suggest to you this solution
function findQuestions(array) {
const questions = [];
for (const element of array) {
if (element.type === "question") {
questions.push(element)
}
if ("items" in element) {
questions.push(findQuestions(element.items))
}
}
return questions
}
CodePudding user response:
If you have more than one level of nesting, then try to use recursive approach:
let allItems = [];
const traverseObject = (obj) => {
if (!obj.items || obj.items.length == 0)
return;
for (let item of obj.items)
{
if (item.type === 'question')
allItems.push(item);
if (item.items && item.items.length > 0)
traverseObject(item)
}
}
An example:
const fooJson = {
"uuid": "707a5ffd-68e2-4dbd-b539-128512ba3a0a",
"type": "page",
"items": [
{
"uuid": "9d823429-cc24-444d-a21c-a81357305851",
"title": "1",
"type": "question",
},
{
"type": "section",
"title": "2",
"uuid": "346dec94-124c-4932-bd40-af9dc68f1d27",
"items": [
{
"uuid": "bf0a9ab9-99cc-4833-b3d3-84a97072e85f",
"title": "2.1",
"type": "question",
"items": [
{
"uuid": "bf0a9ab9-99cc-4833-b3d3-84a97072e85f",
"title": "fooBar",
"type": "question",
}
],
}
],
},
{
"type": "section",
"title": "3",
"uuid": "4964096d-0de9-4ab1-ace5-e42516d6b866",
"items": [
{
"uuid": "b2170580-1e2e-4fb4-a7b9-a56b79db21b3",
"title": "3.1",
"type": "question",
}
],
}
],
"params": {
"collapsed": false
}
};
let allItems = [];
const traverseObject = (obj) => {
if (!obj.items || obj.items.length == 0)
return;
for (let item of obj.items)
{
if (item.type === 'question')
allItems.push(item);
if (item.items && item.items.length > 0)
traverseObject(item)
}
}
traverseObject(fooJson);
console.log(allItems)
<iframe name="sif5" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
You can use a recursive generator function:
function* get_titles(d){
if (d.type === 'question'){
yield {title:d.title}
}
for (var i of ('items' in d ? d.items : [])){
yield* get_titles(i)
}
}
var result = [...get_titles(data)]