0

I'm struggling with manipulating a Javascript object and will appreciate your advice:

I have the following object:

const source = {
  id: '1',
  name: 'Customer A',
  projects: [
    {
      id: '10',
      name: 'Project 2',
      description: 'Project 2 description',
      products: [
        {
          id: '100',
          name: 'Product 1',
          vendor: 'Vendor 1',
          instances: [
            {
              id: '1000',
              operatingSystem: 'Microsoft Windows 2012R2',
              environment: 'Prod',
              version: '4.1',
              notes: '',
            },
          ],
        },
        {
          id: '200',
          name: 'Product 2',
          vendor: 'Vendor 2',
          instances: [
            {
              id: '2000',
              operatingSystem: 'Microsoft Windows 2016',
              environment: 'Prod',
              version: '4.0',
              notes: '',
            },
          ],
        },
      ],
    },
    {
      id: '20',
      name: 'Project 1',
      description: 'Project 1 description',
      products: [
        {
          id: '200',
          name: 'Product 2',
          vendor: 'Vendor 2',
          instances: [
            {
              id: '2000',
              operatingSystem: 'Microsoft Windows 2016',
              environment: 'Prod',
              version: '4.0',
              notes: '',
            },
            {
              id: '3000',
              operatingSystem: 'RedHat Linux 7',
              environment: 'Prod',
              version: '3.12',
              notes: '',
            },
          ],
        },
      ],
    },
  ],
};

I would like to extract from the object above a list of instances grouped by products (this part is working fine):

const products = [
  {
    id: '100',
    name: 'Product 1',
    vendor: 'Vendor 1',
    instances: [
      {
        id: '1000',
        operatingSystem: 'Microsoft Windows 2012R2',
        environment: 'Prod',
        version: '4.1',
        notes: '',
      },
    ],
  },
  {
    id: '200',
    name: 'Product 2',
    vendor: 'Vendor 2',
    instances: [
      {
        id: '2000',
        operatingSystem: 'Microsoft Windows 2016',
        environment: 'Prod',
        version: '4.0',
        notes: '',
      },
      {
        id: '2000',
        operatingSystem: 'Microsoft Windows 2016',
        environment: 'Prod',
        version: '4.0',
        notes: '',
      },
      {
        id: '3000',
        operatingSystem: 'RedHat Linux 7',
        environment: 'Prod',
        version: '3.12',
        notes: '',
      },
    ],
  },
];

The above is achieved by mapping the projects, flattening the products-array, and reducing the results. My next goal is to add each instance the projects it associated to. I need to attach the project id and project name. In the example above, you can see the instance with the '2000' id is associated with 2 projects, and therefore, the expected results should look like this:

const expected = [
  {
    id: '100',
    name: 'Product 1',
    vendor: 'Vendor 1',
    instances: [
      {
        id: '1000',
        operatingSystem: 'Microsoft Windows 2012R2',
        environment: 'Prod',
        version: '4.1',
        notes: '',
        projects: [
          {
            id: '10',
            name: 'Project 2',
          },
        ],
      },
    ],
  },
  {
    id: '200',
    name: 'Product 2',
    vendor: 'Vendor 2',
    instances: [
      {
        id: '2000',
        operatingSystem: 'Microsoft Windows 2016',
        environment: 'Prod',
        version: '4.0',
        notes: '',
        projects: [
          {
            id: '10',
            name: 'Project 2',
          },
          {
            id: '20',
            name: 'Project 1',
          },
        ],
      },
      {
        id: '3000',
        operatingSystem: 'RedHat Linux 7',
        environment: 'Prod',
        version: '3.12',
        notes: '',
        projects: [
          {
            id: '20',
            name: 'Project 1',
          },
        ],
      },
    ],
  },
];

I tried to manipulate the array by several 'forEach' loops, maps, and so on but with no success. Would appreciate having your ideas with how it can be achieved.

2 Answers 2

1
const { inspect } = require('util'); // if Node.js

const source = {
  id: '1', name: 'Customer A', projects: [
    {
      id: '10', name: 'Project 2', description: 'Project 2 description', products: [
        {
          id: '100', name: 'Product 1', vendor: 'Vendor 1', instances: [
            { id: '1000', operatingSystem: 'Microsoft Windows 2012R2', environment: 'Prod', version: '4.1', notes: '', },
          ],
        },
        {
          id: '200', name: 'Product 2', vendor: 'Vendor 2', instances: [
            { id: '2000', operatingSystem: 'Microsoft Windows 2016', environment: 'Prod', version: '4.0', notes: '', },
          ],
        },
      ],
    },
    {
      id: '20', name: 'Project 1', description: 'Project 1 description', products: [
        {
          id: '200', name: 'Product 2', vendor: 'Vendor 2', instances: [
            { id: '2000', operatingSystem: 'Microsoft Windows 2016', environment: 'Prod', version: '4.0', notes: '', },
            { id: '3000', operatingSystem: 'RedHat Linux 7', environment: 'Prod', version: '3.12', notes: '', },
          ],
        },
      ],
    },
  ],
};

const projectToIdMap = source.projects.reduce((projectToIdMap, { name, products }) => {
  projectToIdMap[name] = [];
  products.forEach(({ instances }) => {
    instances.forEach(({ id }) => {
      projectToIdMap[name].push(id);
    });
  });
  return projectToIdMap;
}, {});

const products = [
  {
    id: '100', name: 'Product 1', vendor: 'Vendor 1', instances: [
      { id: '1000', operatingSystem: 'Microsoft Windows 2012R2', environment: 'Prod', version: '4.1', notes: '', },
    ],
  },
  {
    id: '200', name: 'Product 2', vendor: 'Vendor 2', instances: [
      { id: '2000', operatingSystem: 'Microsoft Windows 2016', environment: 'Prod', version: '4.0', notes: '', },
      { id: '2000', operatingSystem: 'Microsoft Windows 2016', environment: 'Prod', version: '4.0', notes: '', },
      { id: '3000', operatingSystem: 'RedHat Linux 7', environment: 'Prod', version: '3.12', notes: '', },
    ],
  },
];

products.forEach(({ instances }) => {
  instances.forEach(instance => {
    instance.projects = [];
    const { id } = instance;
    Object.entries(projectToIdMap).forEach(([project, os], i) => {
      if (projectToIdMap[project].includes(id)) {
        instance.projects.push({ id: (i + 1) * 10, project });
      }
    });
  });
});

console.log(inspect(products, false, null, true)); // if Node.js

This does not remove the duplicate Microsoft Windows 2016 entry, but I'm sure you can take it from here.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for spending your time to help! The key to identifying unique instances should be its ID and not the operating system. The OS is only a string and could be anything (the data come from REST API). The results of both functions return duplicated products and not an OS. I can take it from here as you said but the question is - do this the expected results or I'm missing something?
Deep dive into code, I found an issue that the duplicated product at the end actually contains different instance, so removing the duplicated is actually removing an instance that I need to merge... to better explain the issue, I wrote a quick codesandbox example, do you mind to take a look? codesandbox.io/s/javascript-object-manipulation-2tv8x?file=/src/…
If you post a new question with your current code inside a snippet, along with your expected JSON inside a code block, I'll take a look.
I will try to play with it a bit more and merge the objects - If I'll succeed I'll post the answer if I don't I will post a new question with your instructions. As again, thanks for your time and effort!
0

Ok, I think I made it :)

Thanks @GirkovArpa for the assistance!

You can find the source object in the code snippet or in the original question.

The expected object is:

const expected = [
  {
    id: '100',
    name: 'Product 1',
    vendor: 'Vendor 1',
    instances: [
      {
        id: '1000',
        operatingSystem: 'Microsoft Windows 2012R2',
        environment: 'Prod',
        version: '4.1',
        notes: '',
        projects: [
          {
            id: '10',
            name: 'Project 2',
          },
        ],
      },
    ],
  },
  {
    id: '200',
    name: 'Product 2',
    vendor: 'Vendor 2',
    instances: [
      {
        id: '2000',
        operatingSystem: 'Microsoft Windows 2016',
        environment: 'Prod',
        version: '4.0',
        notes: '',
        projects: [
          {
            id: '10',
            name: 'Project 2',
          },
          {
            id: '20',
            name: 'Project 1',
          },
        ],
      },
      {
        id: '3000',
        operatingSystem: 'RedHat Linux 7',
        environment: 'Prod',
        version: '3.12',
        notes: '',
        projects: [
          {
            id: '20',
            name: 'Project 1',
          },
        ],
      },
    ],
  },
];

First I added each instance its project in the form of an array

Then I flatten the products array results so it will not be split into objects

Then I used @Yevgen Gorbunkov help from the following thread: Merge duplicate items in array of objects, concating nested arrays

I changed his function to also remove duplicate products while merging associated projects (this is why I initialize the project field with an array in the first place)

If you will find a better or more efficient wat to improve the method - I will love to hear :)

EDIT

I changed the code snippet from

dupe.instances = { ...o.instances };

to

dupe.instances = Object.values({ ...o.instances });

for adding the instances as an array instead of an object.

/* Source object */
const source = {
  id: '1', name: 'Customer A', projects: [
    {
      id: '10', name: 'Project 2', description: 'Project 2 description', products: [
        {
          id: '100', name: 'Product 1', vendor: 'Vendor 1', instances: [
            { id: '1000', operatingSystem: 'Microsoft Windows 2012R2', environment: 'Prod', version: '4.1', notes: '', },
          ],
        },
        {
          id: '200', name: 'Product 2', vendor: 'Vendor 2', instances: [
            { id: '2000', operatingSystem: 'Microsoft Windows 2016', environment: 'Prod', version: '4.0', notes: '', },
          ],
        },
      ],
    },
    {
      id: '20', name: 'Project 1', description: 'Project 1 description', products: [
        {
          id: '200', name: 'Product 2', vendor: 'Vendor 2', instances: [
            { id: '2000', operatingSystem: 'Microsoft Windows 2016', environment: 'Prod', version: '4.0', notes: '', },
            { id: '3000', operatingSystem: 'RedHat Linux 7', environment: 'Prod', version: '3.12', notes: '', },
          ],
        },
      ],
    },
  ],
};

/* flattenArray function */
function flattenArray(data) {
  const initialValue = [];

  return data.reduce((total, value) => {
    return total.concat(Array.isArray(value) ? flattenArray(value) : value);
  }, initialValue);
}

/* Adding an array of project on each instance */
source.projects.forEach(project => {
    project.products.forEach(product => {
      product.instances.forEach(instance => {
        instance.project = [
          {
            id: project.id,
            name: project.name
          }
        ];
      });
    });
  });

/* flatten the array */
let products = flattenArray(source.projects.map(p => p.products));

/* reducing products and merging id equaly instances + projects */
products = [
  ...products
    .reduce((r, o) => {
      const dupe = r.get(o.id);
      if (dupe) {
        const a = flattenArray(dupe.instances).reduce(i => i);
        o.instances.forEach(i => {
          if (i.id === a.id) {
            const merged = [...i.project, ...a.project];
            i.project = merged;
          }
        });

        dupe.instances = Object.values({ ...o.instances });
      } else {
        r.set(o.id, o);
      }

      return r;
    }, new Map())
    .values()
];
console.log("products", products);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.