I have this array:
data: [ '+section1+', 'big text 1 here.', 'big text 2 here followed by list:', '-this is list item;', '-this is another list item;', '+section2+', 'big text 3 here followed by list:', '-this is list item;', '-this is another list item;', '-this is another list item;', '+section3+', 'big text 4 here.' ],
I want to transform that array into
"data": [ { section: "section1", content: [ { "text": "big text 1 here.", "list": [] }, } { "text": "big text 2 here followed by list:", "list": [ "-this is list item;", "-this is another list item;" ] }, ] } { section: "section2" content:[ { "text": "big text 3 here followed by list:", "list": [ "-this is list item;", "-this is another list item;", "-this is another list item;" ] }, ] } { section: "section3", content: [ { "text": "big text 4 here.", "list": [] } ] } ]
I have this code that doing about that:
interface DataItem { text: string list: string[] } function isListItem(item: string): boolean { return item.startsWith('-') && item.endsWith(';') } const transformedData = data.reduce<DataItem[]>((acc, item) => { // if item is list item if (isListItem(item)) { // get last DataItem from acc const lastDataItem = acc[acc.length - 1] // and use it as list item's parent if (lastDataItem) { lastDataItem.list.push(item) } else { console.error(`Parent text not found for list item ${item}`) } return acc } // if item is not list item, use it as new parent/DataItem const dataItem: DataItem = { text: item, list: [] } return [...acc, dataItem] }, [])
What it missing is ignoring the sections. Any ideas how to modify that reduce function so it will produce expected result?
To determine a section I came up with this funciton:
function isSection(item: string): boolean { return item.startsWith('+') && item.endsWith('+') }
Advertisement
Answer
I would use the same approach from my answer to another (formerly asked) question of the OP … “Transform array in to object with custom properties” … and adapt it according to the OP’s new requirements.
Thus one would stay with the reduce
based approach which does not depend on outer scope references for keeping track of the currently to be built/aggregated property but instead makes this information part of the reducer function’s first parameter, the previousValue
which serves as an accumulator/collector object.
As for the OP’s task, this collector would feature 2 properties, the sectionKey
and the result
, where the former holds the state of the currently processed property name, and the latter being the programmatically built result
-array of section-items.
function aggregateStructuredTextItem( { sectionKey = '', result = [] }, item ) { // try to retrieve the section specific text. let text = (item.match(/^+s*(.*?)s*+$/) || [])[1] ?? null; if ( (text !== null) && (text !== sectionKey) ) { // keep track of the currently processed section. sectionKey = text; // create and collect new section entry. result.push({ section: text, content: [] }); } else { // access the currently processed section. const section = result.at(-1); // try to retrieve the list specific text. text = (item.startsWith('-') && item.slice(1)); if (text !== false) { // push list specific text into // the most recent content list. section.content.at(-1).list.push(text); } else { // create a content item and push it // into the most recent section content. section.content.push({ text: item, list: [] }); } } return { sectionKey, result }; } const textData = [ '+section1+', 'big text 1 here.', 'big text 2 here followed by list:', '-this is list item;', '-this is another list item;', '+section2+', 'big text 3 here followed by list:', '-this is list item;', '-this is another list item;', '-this is another list item;', '+section3+', 'big text 4 here.' ]; const structuredTextData = textData .reduce(aggregateStructuredTextItem, { result: [] }) .result; console.log({ textData, structuredTextData });
.as-console-wrapper { min-height: 100%!important; top: 0; }