Álvaro
Álvaro

Reputation: 2598

Merge objects according to their keys and values

I have edited the original question as the example did not make the desired output clear, namely I did not specify that I needed the strings that are common with the object that matches the Rank

I am trying to merge an array of objects into a single object

There are three sets of keys per object, I would like to have just one instance of each set with its lowest number for the Rank value or if the Rank equals '-' on all objects.

let objs = [
  {
    Keyword: 'A keyword',
    'First Rank': '-',
    'First Title': '-',
    'First URL': '-',
    'Second Rank': '-',
    'Second Title': '-',
    'Second URL': '-',
    'Third Rank': 1,
    'Third Title': 'Title for 1',
    'Third URL': 'https://for-one.example.com'
  },
  {
    Keyword: 'A keyword',
    'First Rank': '-',
    'First Title': '-',
    'First URL': '-',
    'Second Rank': 2,
    'Second Title': 'Title for 2',
    'Second URL': 'https://for-two.example.com',
    'Third Rank': '-',
    'Third Title': '-',
    'Third URL': '-'
  },
  {
    Keyword: 'A keyword',
    'First Rank': '-',
    'First Title': '-',
    'First URL': '-',
    'Second Rank': '-',
    'Second Title': '-',
    'Second URL': '-',
    'Third Rank': 7,
    'Third Title': 'Title for 7',
    'Third URL': 'https://for-seven.example.com'
  }
]

// I have managed to get the ones with values with this:


const clean = objs.reduce((acc, object) => {
  const clone = (({ Keyword, ...obj }) => obj)(object)

  if (Object.values(clone).some(val => val !== '-')) {
    Object.keys(clone).forEach(key => object[key] === '-' && delete object[key])
    acc.push(object)
  }

  return acc
}, [])

const merged = clean.reduce((result, current) => ({ ...current, ...result }), {})

console.log(merged)

But I am struggling to add the one with no values, it should look like this:

{
  'Keyword': 'A keyword',
  'First Rank': '-',
  'First Title': '-',
  'First URL': '-',
  'Second Rank': 2,
  'Second Title': 'Title for 2',
  'Second URL': 'https://for-two.example.com',
  'Third Rank': 1,
  'Third Title': 'Title for 1',
  'Third URL': 'https://for-one.example.com'
}

Any ideas? Thanks!

Upvotes: 1

Views: 114

Answers (4)

Álvaro
Álvaro

Reputation: 2598

Thanks to everyone who answered this question, however I did not explain in detail some requirements and when I did it was too late to get a follow up response, so I made my own, I am sure there is a better way to do it, but this works.

let objs = [
  {
    'Keyword': 'A keyword',
    'First Rank': '-',
    'First Title': '-',
    'First URL': '-',
    'Second Rank': '-',
    'Second Title': '-',
    'Second URL': '-',
    'Third Rank': 1,
    'Third Title': 'Title for 1',
    'Third URL': 'https://for-one.example.com'
  },
  {
    'Keyword': 'A keyword',
    'First Rank': '-',
    'First Title': '-',
    'First URL': '-',
    'Second Rank': 2,
    'Second Title': 'Title for 2',
    'Second URL': 'https://for-two.example.com',
    'Third Rank': '-',
    'Third Title': '-',
    'Third URL': '-'
  },
  {
    'Keyword': 'A keyword',
    'First Rank': '-',
    'First Title': '-',
    'First URL': '-',
    'Second Rank': '-',
    'Second Title': '-',
    'Second URL': '-',
    'Third Rank': 7,
    'Third Title': 'Title for 7',
    'Third URL': 'https://for-seven.example.com'
  }
]

// Remove dashed entries and keep only the ones with data
const removeDashed = objs.reduce((acc, object) => {
  // Clone object except for the keyword key
  const clone = (({ Keyword, ...obj }) => obj)(object)
  if (Object.values(clone).some(val => val !== '-')) {
    Object.keys(object).forEach(key => object[key] === '-' && delete object[key])
    acc.push(object)
  }
  return acc
}, [])

// Merge array into a single object with first entries found first
const merged = removeDashed.reduce((result, current) => ({ ...current, ...result }), {})

// Items that should be present
const items = ['First', 'Second', 'Third']

// Get the existing keys from the unique obj
const keys = Object.keys(merged).map(key => key.split(' ')[0])

// If one or more items are missing insert them and add dashed values
items.forEach(item => {
  if (!keys.includes(item)) {
    merged[`${item} Rank`] = '-'
    merged[`${item} Title`] = '-'
    merged[`${item} URL`] = '-'
  }
})

// Sort function to reorder | thanks to Soc for this
const sorting = item => (a, b) => {
  const alphaOrder = a < b ? -1 : a > b ? 1 : 0

  if (a === 'Keyword') return -1
  if (b === 'Keyword') return 1
  if (a.startsWith(item) && b.startsWith(item)) return alphaOrder
  if (a.startsWith(item)) return -1
  if (b.startsWith(item)) return 1

  return alphaOrder
}

// Implement the sorting
const result = Object.keys(merged)
  .sort(sorting(items[0]))
  .reduce((acc, current) => {
    acc[current] = merged[current]
    return acc
  }, {})

console.log(result)

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386881

You could reduce the array and check the key of the entries.

let array = [{ 'Keyword': 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': '-', 'Second Rank': '-', 'Second Title': '-', 'Third URL': '-', 'Third Rank': '-', 'Third Title': '-' }, { 'Keyword': 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': '-', 'Second Rank': '-', 'Second Title': '-', 'Third URL': 'https://for-seven.example.com', 'Third Rank': 7, 'Third Title': 'Title' }, { 'Keyword': 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': 'https://for-eleven.example.com', 'Second Rank': 11, 'Second Title': 'Title', 'Third URL': '-', 'Third Rank': '-', 'Third Title': '-' }, { 'Keyword': 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': '-', 'Second Rank': '-', 'Second Title': '-', 'Third URL': 'https://for-one.example.com', 'Third Rank': 1, 'Third Title': 'Title' }],
    merged = array.reduce(({ ...r }, o) => {
        var prefix = ['First', 'Second', 'Third'].find(k => o[k + ' Rank'] !== '-'),
            rank = prefix + ' Rank';

        if (r[rank] < o[rank]) return;
        ['URL', 'Rank', 'Title'].forEach(k => {
            var key = `${prefix} ${k}`
            r[key] = o[key];
        });
        return r;
    });
    
console.log(merged);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

Ele
Ele

Reputation: 33736

You can reduce and within the handler check for the number value in order to compare which is the lowest between the two values.

In this code snippet there is an additional object with Third Rank = 1

let array = [{ Keyword: 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': '-', 'Second Rank': '-', 'Second Title': '-', 'Third URL': '-', 'Third Rank': '-', 'Third Title': '-' }, { Keyword: 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': '-', 'Second Rank': '-', 'Second Title': '-', 'Third URL': 'https://example.com', 'Third Rank': 7, 'Third Title': '2 third title' }, { Keyword: 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': 'https://example.com', 'Second Rank': 11, 'Second Title': '3 Second title', 'Third URL': '-', 'Third Rank': '-', 'Third Title': '-' }, { Keyword: 'A keyword', 'First URL': '-', 'First Rank': '-', 'First Title': '-', 'Second URL': 'https://example.com', 'Second Rank': 11, 'Second Title': '3 Second title', 'Third URL': '-', 'Third Rank': 1, 'Third Title': '-' }],
    merged = array.reduce(({ ...r }, o) => {
        Object.entries(o).forEach(([k, v]) => {
            if (v !== '-') {
              if (isNaN(+v) || isNaN(+r[k])) r[k] = v;
              else r[k] = Math.min(+r[k], +v);
            }
        });
        return r;
    });
    
console.log(merged);

Upvotes: 2

qiAlex
qiAlex

Reputation: 4356

Try this one

let objs = [
  {
    'Keyword': 'A keyword',
    'First URL': '-',
    'First Rank': '-',
    'First Title': '-',
    'Second URL': '-',
    'Second Rank': '-',
    'Second Title': '-',
    'Third URL': '-',
    'Third Rank': '-',
    'Third Title': '-'
  },
  {
    'Keyword': 'A keyword',
    'First URL': '-',
    'First Rank': '-',
    'First Title': '-',
    'Second URL': '-',
    'Second Rank': '-',
    'Second Title': '-',
    'Third URL': 'https://example.com',
    'Third Rank': 7,
    'Third Title': 'Title'
  },
  {
    'Keyword': 'A keyword',
    'First URL': '-',
    'First Rank': '-',
    'First Title': '-',
    'Second URL': 'https://example.com',
    'Second Rank': 11,
    'Second Title': 'Title',
    'Third URL': '-',
    'Third Rank': '-',
    'Third Title': '-'
  },
  {
    'Keyword': 'A keyword',
    'First URL': '-',
    'First Rank': '-',
    'First Title': '-',
    'Second URL': '-',
    'Second Rank': '-',
    'Second Title': '-',
    'Third URL': 'https://example.com',
    'Third Rank': 1,
    'Third Title': 'Title overwritten'
  },
]


var result = objs.slice().reduce((acc, item, index) => {
  resultItem = acc.find(accItem => accItem['Keyword'] === item['Keyword']);
  if (!resultItem) {
    acc.push(item)
  } else {
    Object.keys(resultItem).map(k => {
      if (item[k] !== '-') {
        if (typeof item[k] === 'number') {
          resultItem[k] = resultItem[k] === '-' ? item[k] : Math.min(item[k], resultItem[k]);
        } else {
          resultItem[k] = item[k];
        }
      }
    })
  }
  return acc;  
}, []);

console.log(result);

Upvotes: 2

Related Questions