Array map returning array of undefined values

I am iterating over a couple of JSON arrays of objects and comparing properties to add a property from one to the other. It is returning an array with all undefined values. I am not sure why, any suggestions?

let urls = [facultyBiosJSON, facultyDirectoryJSON];

async function getJson() {
    let facultyArray;
    const [ data1, data2 ] = await Promise.all([
        fetch(urls[0]).then(response => response.json()),
        fetch(urls[1]).then(response => response.json())
        ]);
    facultyArray = data1[0].users.map(user1 => {
        data2[0].users.map(user2 => {
            if(user1.name === user2.name){
                return {...user1, title: user2.title}
            }
            return user2;
        });
    });
    random(facultyArray); // pulls 6 random users from the new array
}

Hi @svoltmer1
Without saying a lot, the first cause of returning undefined here is that you are returning nothing !

I leave some comment below check it.

PS: I dind’t respond directly to your code, respond it yourself so you will remember this whenever you got this problems, keep going and keep learning js is colol.

1 Like

I split it up into a number of functions — a bit cleaner that way.

const getRandomItems = (arr, num = 5) => {
  const shuffled = [...arr].sort(() => 0.5 - Math.random());
  const numToReturn = Math.min(arr.length, num);

  return shuffled.slice(0, numToReturn);
}

// separate function to fetch Json from one file
async function fetchJson(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error(error);
  }
}

// separate function to fetch multiple Json files.
async function fetchAll(urls = []) {
  return await Promise.all(urls.map(fetchJson));
}

async function getFacultyUsers(url1, url2) {
  const [data1, data2] = await fetchAll([url1, url2])
  
  const users1 = data1[0].users;
  const users2 = data2[0].users;
  
  // return all the users
  return users1.map((user) => {
    const targetName = user.name;
    // find user object with same name
    const foundUser = (users2.find((user) => user.name === targetName));
    
    // if match found return with amended title
    // or user object as is
    return (foundUser !== undefined)
      ? { ...user, title: foundUser.title }
      : user;
  })
}

// just an example using the above functions
async function doSomethingHere() {
  
  const facultyUsers = await getFacultyUsers(
    'https://url1here...',
    'https://url2here...'
  )
  
  // Makes more sense to me to get randomUsers
  // outside of the getFacultyUsers function
  const randomUsers = getRandomItems(users, 6)
  ...
}

I don’t know your data, so this is all guess work. Array.find may not be usable.

Thanks, but I’ve tried adding a return statement in front of the first map, but it then returns a copy of the array for all the iterations.

@svoltmer1 It’s difficult to give a proper solution without seeing a sample of the data you are working with.

I have no idea on what data you treat but the answer @rpg_digital give some idea using find may work instead of map in the scopes of the first map

Your right. It is two JSON arrays, one with a “title” property and one with a “bio” property, I need to add the title property to the JSON array of objects with the bio property, based on the users.name property.

So, this is the basic structure. There is a large array that I only need the corresponding titles for faculty in the other array of objects.

[
{"users": [
           {"name": "Tim Blackburn", "title": "Assistant Professor of Chemistry", "bio": "Tim is an avid..." },
           {"name": "Greg Fortner", "title": "Director of Academic Affairs", "bio": "Greg likes kites..." },
          ]
}
]

Sorry so are we talking data a bit like this?

const bios = [
  {"users":
    [
      {"name": "Tim Blackburn", "bio": "Tim is an avid..." },
      {"name": "Greg Fortner", "bio": "Greg likes kites..." },
      {"name": "John Smith", "bio": "John likes to fish..." }
    ]
  }
]

const titles = [
  {"users":
    [
      {"name": "Tim Blackburn", "title": "Assistant Professor of Chemistry", },
      {"name": "Greg Fortner", "title": "Director of Academic Affairs", },
      {"name": "John Smith", "title": "Headmaster" }
    ]
  }
]

Testing with similar code to my previous post

function getFacultyUsers(bios, titles) {
  const userBios = bios[0].users;
  const userTitle = titles[0].users;

  // return all the users
  return userBios.map((user) => {
    const targetName = user.name;
    // find user object with same name
    const foundUser = (userTitle.find((user) => user.name === targetName));
    
    // if match found return with amended title
    // or user object as is
    return (foundUser !== undefined)
      ? { ...user, title: foundUser.title }
      : user;
  })
}

console.log(getFacultyUsers(bios, titles));

Outputs

[
  {name: 'Tim Blackburn', bio: 'Tim is an avid...', title: 'Assistant Professor of Chemistry'},
  {name: 'Greg Fortner', bio: 'Greg likes kites...', title: 'Director of Academic Affairs'},
  {name: 'John Smith', bio: 'John likes to fish...', title: 'Headmaster'}
]

Is that what you are after?

If it is, in theory the last two functions I wrote could be amended to

// just deals with the json data now,
// no async fetching. Easier to test this way.
function getFacultyUsers(bios, titles) {
  const userBios = bios[0].users;
  const userTitle = titles[0].users;

  // return all the users
  return userBios.map((user) => {
    const targetName = user.name;
    // find user object with same name
    const foundUser = (userTitle.find((user) => user.name === targetName));
    
    // if match found return with amended title
    // or user object as is
    return (foundUser !== undefined)
      ? { ...user, title: foundUser.title }
      : user;
  })
}

// do our fetching here instead
async function doSomethingHere() {

  const urls = [
    'https://biosUrlhere...',
    'https://titlesUrlhere...'
  ]
    
  const [bios, titles] = await fetchAll(urls)
  const users = getFacultyUsers(bios, titles)
  const randomUsers = getRandomItems(users, 6)
  ...
}

codepen

Something that crossed my mind last night, what if you have multiple users with the same name? Is there no userId?

Maybe over-engineering here, but I was thinking about how you could use a lookup instead of using Array.find.

I came up with a generic higher-order function to create the lookup.

/**
 @PARAMS {FUNCTION, ARRAY}
 @RETURNS {OBJECT}
 Expects a callback Function and an Array of Objects
 The callback Function should return a tuple [key, prop]
*/
const createFromPairs = (fn, source = []) => {
  const target = {};

  for (let i = 0, len = source.length; i < len; i++) {
    // passes in the source item, the index and the source
    // to a callback function
    const [key, prop] = fn(source[i], i, source);
    target[key] = prop;
  }
  
  return target;
}

Usage

Example data:

const bios = [
  {
    users: [
      { userId: 1, name: "Tim Blackburn", bio: "Tim is an avid..." },
      { userId: 2, name: "Greg Fortner", bio: "Greg likes kites..." },
      { userId: 3, name: "John Smith", bio: "John likes to fish..." },
      { userId: 4, name: "Sally Brown", bio: "Sally is a martial artist..." }
    ]
  }
]

const titles = [
  {
    users: [
      { userId: 1, name: "Tim Blackburn", title: "Assistant Professor of Chemistry" },
      { userId: 2, name: "Greg Fortner", title: "Director of Academic Affairs" },
      { userId: 4, name: "Sally Brown", title: "Headmistress" }
    ]
  }
]

Create a lookup

const lookup = createFromPairs(
  // name becomes key, title becomes value
  (user) => ([user.name, user.title]),
  // source array
  titles[0].users
);

Output:

{
  "Greg Fortner": "Director of Academic Affairs"
  "Sally Brown": "Headmistress"
  "Tim Blackburn": "Assistant Professor of Chemistry"
}

Obviously using the userId’s would make more sense, but keeping inline with the brief.

Merging the props:

With the lookup you can then create your merged objects as follows:

const userBios = bios[0].users

userBios.map((user) => {
  // look up the name in titles
  return (user.name in lookup)
    ? { ...user, title: lookup[user.name] }
    : user;
});

Here is a codepen to demonstrate

There won’t be users with the same name. There is a property named “filename”, which refers to their actual profile page on the website. It is totally unique to the user’s profile page. I guess I should be testing for that instead.

1 Like

Another thing that I see as an issue. When using the find(user), I needed to exclude users that don’t have a user.image or a user.image.includes(“Faculty_Bio-Photoholder.jpg”), but find doesn’t seem to work with multiple conditions. I’ve tried using filter instead, but it’s returning an empty array even when all the conditions are met. Super confused.

function getFacultyUsers(bios, titles) {
  const userBios = bios[0].users;
  const userTitle = titles[0].users;
  return userBios.map((user) => {
    const targetName = user.name;
    const targetImage = user.image;
    // find user object with same name
    const foundUser = (userTitle.find((user) => user.name === targetName && targetImage !== undefined));
    
    return (foundUser !== undefined && foundUser.title !== undefined && !user.image.includes("Faculty_Bio-Photoholder.jpg"))
      ? { ...user, title: foundUser.title }
      : user;
  })
}

What am I missing? Thanks for all your help!

Hi @svoltmer, I have some ideas, but need clarification.

Are the image properties on the users array with bios or the users array with titles?

Can you show me sample of the two array/objects, the one with bios and the one with titles?

Users array with bios.

So I am using the following data as an example

const bios = [
  {users:
    [
      {name: "Tim Blackburn", bio: "Tim is an avid...", image: "./images/tim-blackburn.jpg" },
      // Exclude: Faculty_Bio-Photoholder
      {name: "Greg Fortner", bio: "Greg likes kites...", image: "./images/Faculty_Bio-Photoholder.jpg" }, 
      {name: "Sally Brown", bio: "Sally is a martial artist...", image: "./images/sally-brown.jpg" },
      {name: "John Smith", bio: "John likes to fish...", image: "./images/john-smith.jpg" },
      {name: "Alice Evans", bio: "Alice likes poetry...", image: "./images/alice-evans.jpg" }, 
    ]
  }
];

const titles = [
  {users:
    [
      {name: "Tim Blackburn", title: "Assistant Professor of Chemistry" },
      {name: "Greg Fortner", title: "Director of Academic Affairs"},
      {name: "Sally Brown", title: "Headmistress" },
      // Exclude: no title
      {name: "John Smith" }
      // Exclude: no entry for Alice
    ]
  }
];

My code is similar to yours, apart from that I check for title inside Array.find’s callback

function getFacultyUsers(userBios, userTitles) {

  return userBios.map((userBio) => {
    const targetName = userBio.name;

    const foundUser = (
      userTitles.find(
        // destructure name and title
        ({ name, title }) => {
          return name === targetName && title !== undefined;
        }
      )
    );
    
    // no match return 'userBio' as is
    if (foundUser === undefined) return userBio;
    
    const image = userBio.image;
    
    // no image or the image includes 'Faculty_Bio-Photoholder.jpg' return 'userBio' as is.
    if (image === undefined || image.includes('Faculty_Bio-Photoholder.jpg')) {
      return userBio;
    }
    
    // match ticks all boxes return with title
    return { ...userBio, title: foundUser.title }
  })
}

I’m calling it like this

const userBios = bios[0].users;
const userTitles = titles[0].users;
  
const users = getFacultyUsers(userBios, userTitles);

The output I get is

[
  {
    "name": "Tim Blackburn",
    "bio": "Tim is an avid...",
    "image": "./images/tim-blackburn.jpg",
    "title": "Assistant Professor of Chemistry"
  },
  {
    "name": "Greg Fortner",
    "bio": "Greg likes kites...",
    "image": "./images/Faculty_Bio-Photoholder.jpg"
  },
  {
    "name": "Sally Brown",
    "bio": "Sally is a martial artist...",
    "image": "./images/sally-brown.jpg",
    "title": "Headmistress"
  },
  {
    "name": "John Smith",
    "bio": "John likes to fish...",
    "image": "./images/john-smith.jpg"
  },
  {
    "name": "Alice Evans",
    "bio": "Alice likes poetry...",
    "image": "./images/alice-evans.jpg"
  }
]

Running your code gives the same result.

  1. What is the issue?
  2. What should be the expected result?
  3. Is the source data bios, titles correct?

I found one of the issues and corrected it, but there is another that is perplexing me. It has to do with the JSON data files. One is created by querying all files of content type “Faculty Profile” (the ones with the bios), so it’s getting several old faculty names, that are no longer within the larger JSON data file with the titles. For some reason, the code is still choosing to include these bio profiles even when there isn’t a match for the “name” property against the JSON with the titles (Targetname is somehow matching with name). Here is where I am at.

function getFacultyUsers(bios, titles) {
  const userBios = bios[0].users;
  const userTitles = titles[0].users;

  // return all the users
  return userBios.map((userBio) => {
    const targetName = userBio.name;

    // find user object with same name
    const foundUser = (userTitles.find(({ name, title }) => name === targetName && title !== undefined));
    
    // no match return 'userBio'
    if (foundUser === undefined) return userBio;
    
    const image = userBio.image;
    
    // no image or the image return 'userBio' as is.
   if(image === undefined) {
       return userBio;
   }
   
   // match all tests return with title
   return { ...userBio, title: foundUser.title }
  })
}

What am I missing? So strange.

I think I’ve got it. Instead of returning userBio for when foundUser is undefined and image is undefined (which adds the userBio that didn’t meet the conditions back to the userBios array, just return in both cases. Does that sound correct?

@svoltmer,

I did ask some specific questions. I can only help you if you give me the information.

I appreciate that the data you working on maybe confidential, but without a decent sample and expected output, it is very difficult for me get a clear picture and give you advise — maybe that is just me being a bit dense.

Anyway it seems you are on top of things :slight_smile:

If you do get stuck and can provide a more detailed example of source and output, then hopefully I can help.

Thanks for all your help!! You definitely taught me more about Arrays of Objects. I do have a case that I am still having an issue with, if you could help?

I have the JSON array of titles from earlier (that also includes a department property), and I need to make a separate array of just the department properties and
their values, but I don’t want multiples of the same department value. How could I achieve this?

I think I’ve got this figured out:

// iterate over JSON array of faculty objects and create a new array of department values (excluding some non-academic departments)
function getDepartments(faculty){
            return faculty.map(user => {
                if(user.department != "Academic Affairs" && user.department != "Student Affairs" && user.department != "Communications & Marketing" && user.department != "Enrollment Management" && user.department != "Enterprise Information Technology" && user.department != "IRPA" && user.department != "Learning and Technology" && user.department != "Human & Environmental Services" && user.department != "Institutional Advancement" && user.department != "Finance" && user.department != undefined) {
                    return user.department;
                }
            });
        }

but, I am trying to remove the duplicate values for the department property and somehow ending up with one “undefined” entry using this code:

 function removeDuplicates(array){
            const unique = [];
            array.filter(item => {
                const duplicate = unique.includes(item);
                if(!duplicate) {
                    unique.push(item);
                    return true;
                }
                return false;
            });
            return unique;
        }

Do you see anything that I have written incorrectly? Thanks!