1

I have this array

myarr = [
  '=title1',
  'longText0...',
  'longtText1...',
  '=title2',
  'longTextA...',
  'longtTextB...',
  'longtTextC...'     
];

symbol = indicates that is is a property, next to that is a list of items that belongs to that property

I want to transform that array into object

myObj = {
   title1: [
     'longText0...',
     'longtText1...',   
   ],

   title2: [
     'longTextA...',
     'longtTextB...',  
     'longtTextC...'
   ]
}

I come up with this code so far:

const arrayToObject = (array) =>
   array.reduce((obj, item) => {
     if(item.startsWith('=')) {
       const itemName = item.replace('=', '')
       obj[itemName] = itemName;
     } else {
       
       //add the rest....
     }
 

     return obj
   }, {})


console.log(arrayToObject(myarr))

My challenges so far is that I am not sure how to turn obj[itemName] so I can assign the items to it. Any ideas how to do that?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Sergino
  • 10,128
  • 30
  • 98
  • 159

5 Answers5

2

I wouldn't do that with reduce but with a simple for loop, because you have to carry the itemname over multiple iterations

let o = {}, n = ''; 
for (let k of arr) {
  if (k.startsWith('=')) { 
    n = k.substring(1);
    o[n] = []
  } else {
    o[n].push(k);
  }
}

You can of course also do it with reduce, but you have to put the declaration of itemname outside of the callback

let n = '';
let o = arr.reduce((a, c) => {
  if (c.startsWith('=')) {
    n = c.substring(1);
    a[n] = [];
  } else {
    a[n].push(c);
  }
  return a;
}, {});

Please be aware, there is no error handling, ie the code assumes your array is well structured and the first element in the array must start with =

derpirscher
  • 14,418
  • 3
  • 18
  • 35
  • In terms of code reuse, a **properly named reducer function statement** might even be the favored solution; one just needs to implement the reducer in a way that it keeps track of the currently processed property name. – Peter Seliger Apr 26 '22 at 10:05
  • @PeterSeliger Yes, might be. But at the level of this question, I assumed code reuse is not the major issue here so I just tried to provide a simple and understandable example of how this can be done ... – derpirscher Apr 26 '22 at 12:17
2

A reduce based approach which does not depend on outer scope references for keeping track of the currently to be built/aggregated property makes this information part of the reducer function's first parameter, the previousValue which serves as an accumulator/collector object.

Thus, as for the OP's task, this collector would feature two properties, the currentKey and the result, where the former holds the state of the currently processed property name and the latter being the programmatically built result.

// - reducer function which aggregates entries at time,
//   either by creating a new property or by pushing a
//   value into the currently processed property value.
// - keeps the state of the currently processed property
//   by the accumulator`s/collector's `currentKey` property
//   whereas the result programmatically gets build as
//   the accumulator`s/collector's `result` property.

function aggregateEntry({ currentKey = null, result = {} }, item) {
  const key = (item.startsWith('=') && item.slice(1));
  if (
    (key !== false) &&
    (key !== currentKey)
  ) {
    // keep track of the currently processed property name.
    currentKey = key;

    // create a new entry (key value pair).
    result[currentKey] = [];
  } else {
    // push value into the currently processed property value.
    result[currentKey].push(item);
  }
  return { currentKey, result };
}
console.log([
  '=title1',
  'longText0...',
  'longtText1...',
  '=title2',
  'longTextA...',
  'longtTextB...',
  'longtTextC...',
].reduce(aggregateEntry, { result: {} }).result);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
0

The following function will give you the desired results

    function arrayToObject(arr)
{
    let returnObj={};
    for(let i =0; i <arr.length; i++)
    {
        if(arr[i].startsWith('='))
       {
           let itemName = arr[i].replace('=','');
           returnObj[itemName]=[];
           for(let j=i+1; j <arr.length;j++)
           {
               if(arr[j].startsWith('='))
               {
                   break;
               }
               else
                {
                    let value = arr[j];
                    returnObj[itemName].push(value) ;
                }
               
           }
           
       }
    }
    
    return returnObj;
}
0

Here a version with reduce

const myarr = [
  '=title1',
  'longText0...',
  'longtText1...',
  '=title2',
  'longTextA...',
  'longtTextB...',
  'longtTextC...'     
];


const obj = myarr.reduce((res, el) => {
 if(el.startsWith('=')){
   const key = el.substring(1)
   return {
     data: {
       ...res.data,
       [key]: [],
     },
     key
   }
 }
 return {
  ...res,
   data:{
     ...res.data,
     [res.key]: [...res.data[res.key], el]
   }
 }

}, {
 data: {},
 key: ''
}).data

console.log(obj)
R4ncid
  • 6,944
  • 1
  • 4
  • 18
0

You don't need to keep the key somewhere separate for a reduce method:

const myarr = ['=title1', 'longText0...', 'longtText1...', '=title2', 'longTextA...', 'longtTextB...', 'longtTextC...'];

const res = Object.fromEntries(
  myarr.reduce((acc, item) => {
    if(item.startsWith('='))
      acc.push([item.substring(1), []]);
    else
      acc[acc.length - 1]?.[1].push(item);
    
    return acc;
  }, [])
);

console.log(JSON.stringify( res ));
Ben Stephens
  • 3,303
  • 1
  • 4
  • 8