4

I'm developing with Angular and I have the following Typescript array of objects:

docs = [
    { 
        id: '1',
        type: {
            id: 1
            desc: 'Category 1',
        }
        title: 'Foo",
        date: '2018-06-21',
        attachments: [
            { id: 51, filename: 'foo.pdf', title: 'Foo' },
            { id: 20, filename: 'bar.doc', title: 'Bar' }
        ]
    },
    { 
        id: '2',
        type: {
            id: 2
            desc: 'Category 2',
        }
        title: 'Bar",
        date: '2018-06-21',
        attachments: [
            { id: 15, filename: 'foobar.xls', title: 'Foobar' },
            { id: 201, filename: 'example.doc', title: 'Example' }
        ]
    }
]

I need to get only a subset of the properties, something like this:

docs = [
    { 
        id: '1',
        type: {
            id: 1
            desc: 'Category 1',
        }
        attachments: [
            { id: 51 },
            { id: 20 }
        ]
    },
    { 
        id: '2',
        type: {
            id: 2
            desc: 'Category 2',
        }
        attachments: [
            { id: 15 },
            { id: 201 }
        ]
    }
]

How can I achieve this? Have I to create a parser or does exist any smart way (such as Lodash) to extract a lite version of the array?

smartmouse
  • 13,912
  • 34
  • 100
  • 166
  • Have a look at [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) – Aleksey L. Jun 21 '18 at 14:36
  • I have already seen it, but I cannot figure out how to map multiple properties: `map = array.map(_ => _.x)` – smartmouse Jun 21 '18 at 14:42
  • Just `const result = docs.map(({ id, type, attachments }) => ({id, type, attachments: attachments.map(a => a.id)}));` – Aleksey L. Jun 21 '18 at 14:53

5 Answers5

4

var docs = [{"id":"1","type":{"id":1,"desc":"Category 1"},"title":"Foo","date":"2018-06-21","attachments":[{"id":51,"filename":"foo.pdf","title":"Foo"},{"id":20,"filename":"bar.doc","title":"Bar"}]},{"id":"2","type":{"id":2,"desc":"Category 2"},"title":"Bar","date":"2018-06-21","attachments":[{"id":15,"filename":"foobar.xls","title":"Foobar"},{"id":201,"filename":"example.doc","title":"Example"}]}];

const result = docs.map(({id,type,attachments})=>{
    let doc={id,type};
    doc.attachments=attachments.map(({id})=>({id}));
    return doc;
});

console.log(result);

have a look at this. this works perfectly!

Inus Saha
  • 1,918
  • 11
  • 17
  • Using the same mapping... what about if I would map only attachments with `filename` equals to `example.doc`? – smartmouse Jun 21 '18 at 15:52
  • 1
    `attachments.map(({id})=>({id}));` needs to be replaced by `attachments.filter(att=>att.filename=='example.doc').map(({id})=>({id}));` but keep in mind now attachments may be empty if there is no match found. – Inus Saha Jun 21 '18 at 15:57
1

You can use array.map and object destructuring to extract only the wanted properties.

Also use JSON.parse and JSON.stringify to make a copy and avoid side effetcs.

docs2 = JSON.parse(JSON.stringify(docs)).map(
({id, type, attachements}) => 
  ({ id, 
    type,
    attachements: attachements.map(({id}) => ({id})
  })
)
Nova
  • 11
  • 3
  • Why the `JSON.stringify`? – J. Pichardo Jun 21 '18 at 14:49
  • I used JSON.stringify to make a deep copy of the object. Otherwise, a change of a property like "type.id" for example in the original "docs" will also change "type.id" in the copy. Otherwise you would have to manually copy the properties of "type". Both are valid solutions. – Nova Jun 21 '18 at 14:53
1

You can use Array.map with object spreading, something like this:

const mapSubset = ({ id, type, attachments }) => {
    return { id, type, attachments: attachments.map( {id} => id ) };
};

const subset = docs.map( mapSubset );
Dimitri L.
  • 4,499
  • 1
  • 15
  • 19
0

I was looking for a non-specific way to accomplish this or any other similar cases, so far I've thought of the following:

  • Have an IMapping<T> type, that defines the way to map each property.
  • Have an IMappingFunction<T> interface, that determines how to map a specific thing:

The following code demonstrates it:

type IMapping<T> = {
    [P in keyof T]: IMapping<T[P]> | IMappingFunction<T[P]>;
}

interface IMappingFunction<T>{
    (t: T): T | Partial<T>
}

class Person{
    name: string;
    lastName: string;
}

const obj: IMapping<Person> =  {
    name: s => s.toUpperCase(),
    lastName: s => s
}

function map<T>(obj: T, mapping: IMapping<T>) {
    return Object.keys(obj)
        .map(prop => { 
            const propMapping = mapping[prop];

            return {
                key: prop,
                value: typeof propMapping === 'function' ?
                    propMapping(obj[prop]) :
                    map(obj, propMapping)
            };
        })
        .reduce((acc, prop) => ({...acc, [prop.key]: prop.value}), { });
}

console.log(map({ name: 'Name', lastName: 'LastName'}, obj));

For a runnable snippet check here

J. Pichardo
  • 3,077
  • 21
  • 37
-2

do you need to leave the original array intact? If not you can iterate through the list of objects using a for loop and use the 'delete' operator to delete the properties you no longer want.

For example:

var Employee = {
  firstname: "Mohammed",
  lastname: "Haddad"
}

delete Employee.firstname;

console.log(Employee);
// expected output: { lastname: "Haddad" }
Kevin Aud
  • 378
  • 2
  • 12
  • I read that `delete` is very slow and not well to use... (the downvote it's not mine!) – smartmouse Jun 21 '18 at 14:45
  • I could be wrong, but I believe that only applies if you are using delete to remove elements from an array, not when you are using it to remove properties from an object. – Kevin Aud Jun 21 '18 at 14:50