0

I need to search for a value in nested array to answer the question :

Is there an emailAddress of "type":"CONTACT" with "value":"email4@example.com"?

I wrote the following basic algorithm (see fiddle) which is giving the expected result, but I guess there is a better and shorter way to solve it ...

const infos = [
    {
        "resourceName":"name1",
        "id":"id1",
        "emailAddresses":[
            { "metadata":{ "primary":true,  "source":{ "type":"CONTACT", "id":"m1" } , "value":"email1@example.com"}},
            { "metadata":{ "primary":false, "source":{ "type":"CONTACT", "id":"m2" } , "value":"email2@example.com"}}
        ]
    },
    {
        "resourceName":"name2",
        "id":"id2",
        "emailAddresses":[
            { "metadata":{ "primary":true,  "source":{ "type":"FAMILY", "id":"m3" } , "value":"email3@example.com"}},
            { "metadata":{ "primary":false, "source":{ "type":"CONTACT", "id":"m4" } , "value":"email4@example.com"}},
            { "metadata":{ "primary":false, "source":{ "type":"BUSINESS", "id":"m5" } , "value":"email5@example.com"}}
        ]
    },
    {
        "resourceName":"name3",
        "id":"id3",
        "emailAddresses":[
            { "metadata":{ "primary":true,  "source":{ "type":"CONTACT", "id":"m5" } , "value":"email6@example.com"}},
            { "metadata":{ "primary":false, "source":{ "type":"FAMILY", "id":"m6" } , "value":"email7@example.com"}}
        ]
    }
];

const search_email = "email4@example.com";
let parent_key = null;

infos.forEach( info => {
    info.emailAddresses.forEach( ema => {
        if ((ema.metadata.source.type === 'CONTACT') && (ema.metadata.value === search_email )) {
            parent_key = info.id;
        }
    })
});

if ( parent_key === null) {
    console.log('NOT FOUND');
} else {
    console.log('FOUND - PARENT KEY: ', parent_key);
}

I can stop the search on the first found state... just need to know if the searched email does exist.

Peter B
  • 22,460
  • 5
  • 32
  • 69
  • So, what is your actual question? How to get out of a foreach loop before it has gone over all entries? Well that should be rather trivial to research … – CBroe May 31 '18 at 10:44
  • Possible duplicate of [How to short circuit Array.forEach like calling break?](https://stackoverflow.com/questions/2641347/how-to-short-circuit-array-foreach-like-calling-break) – Peter B May 31 '18 at 10:54

3 Answers3

1

You can use array#some to iterate through infos array, then again use array#some to look inside emailAddresses array where source type is CONTACT and value is the email address you provided.

The some() method tests whether at least one element in the array passes the test implemented by the provided function.

If an element is found, array#some() immediately returns true.

const infos =[ { "resourceName":"name1", "id":"id1", "emailAddresses":[ { "metadata":{ "primary":true, "source":{ "type":"CONTACT", "id":"m1" } , "value":"email1@example.com"}}, { "metadata":{ "primary":false, "source":{ "type":"CONTACT", "id":"m2" }, "value":"email2@example.com"}} ] }, { "resourceName":"name2", "id":"id2", "emailAddresses":[ { "metadata":{ "primary":true, "source":{ "type":"FAMILY", "id":"m3" } , "value":"email3@example.com"}}, { "metadata":{ "primary":false, "source":{ "type":"CONTACT","id":"m4" } , "value":"email4@example.com"}}, { "metadata":{ "primary":false, "source":{ "type":"BUSINESS", "id":"m5" } , "value":"email5@example.com"}} ] }, { "resourceName":"name3", "id":"id3", "emailAddresses":[ { "metadata":{ "primary":true, "source":{"type":"CONTACT", "id":"m5" } , "value":"email6@example.com"}}, { "metadata":{ "primary":false, "source":{ "type":"FAMILY", "id":"m6" } , "value":"email7@example.com"}} ] } ],
    searchEmail = "email4@example.com";
    found = infos.some(({emailAddresses}) =>
              emailAddresses.some(({metadata}) => metadata.source.type === 'CONTACT' && metadata.value === searchEmail)
            );
console.log(found);
Hassan Imam
  • 21,956
  • 5
  • 41
  • 51
  • 1
    Just 1 suggestion. Indent the code so its easy to understand. Its a very long code line. – Rajesh May 31 '18 at 10:53
  • 1
    thanks a lot ... got it and found good doc on it ... very useful methods, no real need for lodash now ... –  Jun 01 '18 at 08:45
0

The functions some, filter and find all take a function with the same signature. The function passed to some, filter and find receive an item of the array and return true or false.

You can then use partially applied functions and combine them to easily build combinable filters, an example of how that is used can be found here

If you need to combine filters then I would suggest having a look at the code below, there are no comments explaining but you can find more information on how it works in the filter example mentioned before.

const infos = [
  {
    "resourceName": "name1",
    "id": "id1",
    "emailAddresses": [
      { "metadata": { "primary": true, "source": { "type": "CONTACT", "id": "m1" }, "value": "email1@example.com" } },
      { "metadata": { "primary": false, "source": { "type": "CONTACT", "id": "m2" }, "value": "email2@example.com" } }
    ]
  },
  {
    "resourceName": "name2",
    "id": "id2",
    "emailAddresses": [
      { "metadata": { "primary": true, "source": { "type": "FAMILY", "id": "m3" }, "value": "email3@example.com" } },
      { "metadata": { "primary": false, "source": { "type": "CONTACT", "id": "m4" }, "value": "email4@example.com" } },
      { "metadata": { "primary": false, "source": { "type": "BUSINESS", "id": "m5" }, "value": "email5@example.com" } }
    ]
  },
  {
    "resourceName": "name3",
    "id": "id3",
    "emailAddresses": [
      { "metadata": { "primary": true, "source": { "type": "CONTACT", "id": "m5" }, "value": "email6@example.com" } },
      { "metadata": { "primary": false, "source": { "type": "FAMILY", "id": "m6" }, "value": "email7@example.com" } }
    ]
  }
];

const filterFn = getter => comparer => o =>
  comparer(getter(o))
;

const getEmailAddresses = o => (o && o.emailAddresses) || [];

const getType = o => o.metadata.source.type;

const getEmail = o => o.metadata.value;

const getPrimary = o => String(o.metadata.primary);

const isEqual = value => item => item === value;

const compareEmail = comparers => items =>
  items.some(
    item=> 
      comparers.reduce(
        (result,comparer)=>result && comparer(item),
        true
      )
  )
;
const getters = [getPrimary,getType,getEmail];
Array.from(document.querySelectorAll("select")).forEach(
  select=>{
    select.addEventListener(
      "change",
      ()=>{
        const filters = Array.from(document.querySelectorAll("select")).map(
          (select,index)=>
            (select.value==="none")
              ? x=>true
              : filterFn(getters[index])(isEqual(select.value))
        );
        console.log(
          infos.filter(filterFn(getEmailAddresses)(compareEmail(filters)))
          .map(item=>item.resourceName)
        );
      }
    )
  }
)
      <select>
        <option value="none">no filter on primary</option>
        <option value="true">primary</option>
        <option value="false">not primary</option>
      </select>
      <select>
        <option value="none">no filter on type</option>
        <option value="CONTACT">CONTACT</option>
        <option value="FAMILY">FAMILY</option>
        <option value="BUSINESS">BUSINESS</option>
      </select>
      <select>
        <option value="none">no filter on email</option>
        <option value="email1@example.com">email1@example.com</option>
        <option value="email2@example.com">email2@example.com</option>
        <option value="email3@example.com">email3@example.com</option>
        <option value="email4@example.com">email4@example.com</option>
        <option value="email5@example.com">email5@example.com</option>
        <option value="email6@example.com">email6@example.com</option>
        <option value="email7@example.com">email7@example.com</option>
      </select>
HMR
  • 37,593
  • 24
  • 91
  • 160
0

You can utilize the Array's some() method in combination with destructuring.

To safeguard from your code potentially failing when some properties are missing in the Objects of your infos Array (i.e. the ones you're searching for), there's a couple of notable things happening in the gist below. They are:

  1. In the line of code which reads:

    .some(({ emailAddresses = [] }) => emailAddresses
    

    Destructuring is utilized. However perhaps more importantly, a default value of an empty Array (i.e. the = [] part) has been specified. This will prevent an error if one of your Objects in the infos Array doesn't include a emailAddresses property. More specifically it will prevent your program failing when the next .some(...) method is invoked, (i.e. the one in the subsequent line of code), when there is no emailAddresses member/property.

  2. The second time the .some method is invoked we are not only returning a Boolean based on the evaluation of:

    metadata.source.type === 'CONTACT' && metadata.value === searchEmail
    

    Because just doing this will cause your program to fail when a metadata Object in the emailAddresses Array doesn't include a source member/property. Instead we initially evaluate metadata.source to check it's existence before evaluating metadata.source.type === 'CONTACT'. So we do this instead:

    metadata.source && metadata.source.type === 'CONTACT' && metadata.value === searchEmail

Note: Comments 1, and 2 in the code sample below indicate where properties/values have been intentionally omitted to demonstrate the program doesn't fail under such circumstances.

const infos = [{
    "resourceName": "name1",
    "id": "id1"
    // 1. Intentional missing `emailAddresses` property
  }, {
    "resourceName": "name2",
    "id": "id2",
    "emailAddresses": [
      { "metadata": { "primary": true, /* 2. Intentional missing `source` property */ "value": "email3@example.com" } },
      { "metadata": { "primary": false, "source": { "type": "CONTACT", "id": "m1" }, "value": "email4@example.com" } },
      { "metadata": { "primary": false, "source": { "type": "BUSINESS", "id": "m2" }, "value": "email5@example.com" } }
    ]
  }, {
    "resourceName": "name3",
    "id": "id3",
    "emailAddresses": [
      { "metadata": { "primary": true, "source": { "type": "CONTACT", "id": "m3" }, "value": "email6@example.com" } },
      { "metadata": { "primary": false, "source": { "type": "FAMILY", "id": "m4" }, "value": "email7@example.com" } }
    ]
  }
];

const searchEmail = "email4@example.com";

const hasFound = infos
    .some(({ emailAddresses = [] }) => emailAddresses
    .some(({ metadata }) => metadata.source
        && metadata.source.type === 'CONTACT'
        && metadata.value === searchEmail
    ));

console.log(hasFound);

An alternative way to safeguard your code:

Alternatively, Nested Object Destructuring with default values can be utilised to help safeguard your code from failing when the properties that you’re searching for don’t exist in the infos Array of Objects. For example:

const hasFound = infos
    .some(({ emailAddresses = [] }) => emailAddresses
    .some(({ metadata: { source = {}, value } }) =>
        source.type === 'CONTACT' && value === searchEmail
    ));

This time in the second .some() method we destructure/navigate into the nested object to retrieve only the information we need, i.e. source and value. By doing this we avoid the need to check-for metadata.source‘s existence , (i.e. when returning the Boolean from the function as per the previous gist), because the source variable gets assigned a default value of an empty Object (i.e. source = {}).

RobC
  • 22,977
  • 20
  • 73
  • 80