Iterating through specific values in an object

I’m trying to check an object created with dropzone.js to see if all the files uploaded have the same file extension.

the object returned is dropzoneForm.files

Here is an example of the object with a 2 file upload:

[
  {
    "upload": {
      "uuid": "2dc0602b-42a0-9258-572c5f0ab5cf",
      "progress": 0,
      "total": 10121,
      "bytesSent": 0,
      "filename": "anyOther5PDF.pdf",
      "chunked": false,
      "totalChunkCount": 1
    },
    "status": "queued",
    "previewElement": {},
    "previewTemplate": {},
    "_removeLink": {},
    "accepted": true
  },
  {
    "upload": {
      "uuid": "0596e7d1-448e-b714-96833cf724f1",
      "progress": 0,
      "total": 861,
      "bytesSent": 0,
      "filename": "anyText.txt",
      "chunked": false,
      "totalChunkCount": 1
    },
    "status": "queued",
    "previewElement": {},
    "previewTemplate": {},
    "_removeLink": {},
    "accepted": true
  }
]

So I’m stuck trying to figure out how to use a function to check all the dropzoneForm.files.filenames.

function areFilesSameType(filesArr) {
    var fileExtension = "";
    for (u = 0; u < filesArr.length; i++)
    {
        if (u == 0) {
            fileExtension = u.split('.').pop();
        }
        if (u > 0 && fileExtension != u.split('.').pop()) {
            return false;
        }
    }
    return true;
}

areFilesSameType(dropzoneForm.files.filename); //should return false as "pdf" != "txt"
2 Likes

You can iterate through an object by using a for (key in object) {//Do Stuff} loop.
However, in this example, you already know exactly what key to use to access the information you need, so I’m not sure object key iteration is necessary (IF I can assume the “object.upload.filename” path exists for all objects).

function areFilesSameType(filesArr) {
    const ext = new RegExp('\\.\\w+$'),  // Regular Expression to retrieve the extension from a string
          fileName = (i) => filesArr[i]["upload"]["filename"],  // Function to retrieve filename string
          extension = (i) => fileName(i).match(ext)[0].toLowerCase();  // Function to retrieve file extension string
    try {
        for (var i = 0; i < filesArr.length - 1; i++) { // Don't need to loop through the last object in array
            // IF file extension of current object is NOT THE SAME as the file extension of the next object
            if (extension(i) != extension(i + 1)) return false;
        };
    }
    catch (TypeError) {
        console.log('No Extension Found');
        return false;
    }
    return true;
}

EDIT: made extension comparison case tolerant & broke out extension getter & No extension match (as per @lasjorg’s point)

1 Like

How big are the arrays going to be? If they are really big you probably do want to use a normal loop.

Anyway, here just for fun is a version (admittedly much less legible) using Set(). I also added toLowerCase just because.

// https://codeburst.io/javascript-array-distinct-5edc93501dc4

function areFilesSameType(filesArr) {
  return (
    // If length is 1 all extensions are the same, so true
    [
      ...new Set(
        filesArr.map(obj => obj.upload.filename.split('.')[1].toLowerCase())
      )
    ].length === 1
  );
}
console.log(areFilesSameType(files));

Not actually sure how good, or slow, it is but it seemed to work with the small sample I tested it on. Also, the formatting is Prettier, not really sure how much I care for it.

Note: Both versions will break if there is a file with no extension. There really should be a check for that I guess.

// Now it's just more ugly but at least wont break on files with no extensions
function areFilesSameType(filesArr) {
  return (
    // If the length is 1 all extensions are the same, so true
    [
      ...new Set(
        filesArr.map(obj => {
          if(obj.upload.filename.split('.')[1])
          return obj.upload.filename.split('.')[1].toLowerCase()
        })
      )
    ].length === 1
  );
}
console.log(areFilesSameType(files));
1 Like

Wow! You guys are wizards!

Is there a name for what you did with const ext? It looks like a series of functions, but all in one line, separated by commas? What’s that all about? Is there a name for that so I can study it further?

EDIT: Hmm, I guess we can’t reply to specific comments. Sorry. These questions were meant for @kylec.

EDIT 2: BTW, thanks to whoever edited my original post so that the object formatting was correct!

@camperextraordinaire True, honestly the case for the missing extension was an afterthought and my solution was really more proof of concept than anything else. Also, it is 4 am for me, my brain is done for the day.

I was referring to my code formatting BTW.

1 Like

I declared 3 Constants. A Regular Expression Object (ext), and two functions in arrow function notation (fileName & extension). Separating each new constant with a comma instead of a semi-colon allows you to chain the initial declaration to other variables. Just some JS shorthand really. The RegExp object can be fed to functions like .search() & .match(), I put it in mostly for code documentation. It could be removed and the function extension() could look like this instead:

const extension = (i) => fileName(i).match(/\.\w+$/)[0].toLowerCase();
const ext = new RegExp('\\.\\w+$'),
      fileName = (i) => filesArr[i]["upload"]["filename"],
      extension = (i) => fileName(i).match(ext)[0].toLowerCase();

Is equivalent to:

const ext = new RegExp('\\.\\w+$');
const fileName = (i) => filesArr[i]["upload"]["filename"];
const extension = (i) => fileName(i).match(ext)[0].toLowerCase();
2 Likes

I like the Set solution. I use it often in Python but always forget about it as an option in JS.

There is something I like about the succinctness of @camperextraordinaire 's solution. I would probably go with that one except @kylec 's is also correct and was first.

Thank you everyone.

Consider filenames like foo.bar~ (an emacs backup file). All these regexes using \w are going to fall down on those. Rather, try a regex like \.([^.]+)$ instead.

1 Like

You simply do this way, check the below code:

var myVariable=[
  {
    "upload": {
      "uuid": "2dc0602b-42a0-9258-572c5f0ab5cf",
      "progress": 0,
      "total": 10121,
      "bytesSent": 0,
      "filename": "anyOther5PDF.pdf",
      "chunked": false,
      "totalChunkCount": 1
    },
    "status": "queued",
    "previewElement": {},
    "previewTemplate": {},
    "_removeLink": {},
    "accepted": true
  },
  {
    "upload": {
      "uuid": "0596e7d1-448e-b714-96833cf724f1",
      "progress": 0,
      "total": 861,
      "bytesSent": 0,
      "filename": "anyText.txt",
      "chunked": false,
      "totalChunkCount": 1
    },
    "status": "queued",
    "previewElement": {},
    "previewTemplate": {},
    "_removeLink": {},
    "accepted": true
  }
];

console.log(myVariable[0].upload.filename); //anyOther5PDF.pdf
console.log(myVariable[1].upload.filename); //anyText.txt

I want to share a resource where values are extracted from the JSON returned from TMDB API - here.

1 Like

Thank you, @dianaug. Your example was definitely the easiest for me to understand.

1 Like

@kylec I have another question if you don’t mind? I am unfamiliar with seeing i being used outside of loops, such as in your

fileName = (i) => filesArr[i]["upload"]["filename"];

Is it just a variable the way you used it? could you replace it with something like

fileName = (whateverIWant) => filesArr[whateverIWant]["upload"]["filename"]; ?

And if it is just a variable, why is it that you never had to declare it? As in

var i; ?

To all,

I’ve noticed that the regex suggestions return the dot, e.g., “.jpg”. Whats the best practice if I don’t want the dot included?

Yes you are correct, those variables could have been named anything. I only chose i because that is what is being fed as an argument to these functions. If I were to rewrite the function more formally or verbosely it might look something like this:

function areFilesSameType(filesArr) {
    /* Function to check if the filename extension is the same for
    all objects in an upload array */

    function getFileName(index) {n: 
        /* Helper function:
        looks for filename in object at index,
        attribute path: 'obj.upload.filename' */
        let obj = filesArr[index];
        return obj["upload"]["filename"];
    };

    function parseExtension(index) {
        /* Helper function:
        Parses a filename string for an extension to pull out
        and return, uses helper getFileName(index) */
        let obj = getFileName(index);
        let ext = obj.match(/\.\w+$/)[0]
        return ext.toLowerCase();
    };

    try {
        // Begin Main Loop
        for (var i = 0; i < filesArr.length - 1; i++) {
            if (parseExtension(i) != parseExtension(i + 1)) {
                return false;
            };
        };

    } catch(TypeError) {
        /* Meant to catch the TypeErrors returned from either
        a 'No Match' scenario  in parseExtension or from a corrupt
        attribute pathway in getFileName() */
        console.log('Could not parse Upload Object Extension');
        console.log('Check Upload Filenames or Attribute Pathway');
        return false;
    };

    // All extensions matched and no Errors were thrown
    return true;
};

To contrast, here’s a compact edition (maybe too much so), also taking into account things like what @chuckadams pointed out and your desire to not incorporate the ‘.’ in the extension. I’m using a Positive Lookbehind to ensure there is a ‘.’ before the match.

function areFilesSameType(arr) {
    const ext = (index) => arr[index].upload.filename.match(/(?<=\.)([^.]+)$/)[0].toLowerCase();
    try {for (var i=0; i<arr.length-1; i++) if (ext(i) != ext(i+1)) return false}
    catch (TypeError) {return false}
    return true;
}
1 Like

Maybe a nice middle-ground might look like this IMO:

function uploadsTypeCheck(uploads, AttrPath='upload.filename') {
    const getFileName = (idx) => {
            const attributes = AttrPath.split('.').filter(c => c ? true : false);
            return attributes.reduce((fn, a) => fn = fn[a], uploads[idx]);
          },
          parseExt = (idx) => getFileName(idx).match(/(?<=\.)([^.]+)$/)[0].toLowerCase();

    try {
        for (var i=0; i<uploads.length-1; i++) if (parseExt(i) != parseExt(i+1)) return false;
    }
    catch (TypeError) {
        console.log('Error parsing an uploads filename extension');
        console.log('Check attribute pathway or desired file name field');
        console.log(TypeError);
        return false;
    };
    return true;
};

It incorporates the best answer regular expression (plus lookbehind) to parse a string for an extension. It should catch errors and lets you know what to do about them. The error traceback can be used to determine if it’s a problem with a particular Upload object’s filename field or with the pathway used to find it depending on if it stops in getFileName() or parseExt(). It also gives you an option to handle changes in Upload Object architecture without growing the function too much. The guts of the try block could realistically be swapped out for any of the solutions brought up in this thread so don’t feel like you have to stick with mine.