Convert a JavaScript Array into a JSON object

735 views Asked by At

I'm modifying a Mail Merge project in Google Apps Script. The problem I'm facing is how to make the email body display inline images. Currently, all inline images present in the original email template are displayed as attachments after GmailApp.sendEmail() is called.

My guess is that the inline images can be displayed if I find some way to convert the imgVars array into a JSON object like this (taken from the example in GAS documentation):

  MailApp.sendEmail(
    "[email protected]",
    "Logos",
    "", 
    { htmlBody: 
      "inline Google Logo<img src='cid:googleLogo'> images! <br/> inline YouTube Logo   <img src='cid:youTubeLogo'>",
     inlineImages: 
     { googleLogo: googleLogoBlob,
       youTubeLogo: youtTubeLogoBlob
     }
    }
  );

So what I'm trying to do is convert an array like this:

var array = { item1, item2, item3 };

To a JSON object like this:

var json = { item1Name: item1,
             item2Name: item2,
             item3Name: item3
           };

Here is a code snippet from the Mail Merge I'm working on:

  //---------------------------------------------------------------
  // If there are inline images in the body of the email
  // Find them and store them in an array, imgVars
  //---------------------------------------------------------------
  if(emailTemplate.search(/<\img/ != -1)) {  
  var inlineImages = {};

  // Extract all images from the email body
  var imgVars = emailTemplate.match(/<img[^>]+>/g);

  // For each image, extract its respective title attribute
  for (i in imgVars) {
    var title = imgVars[i].match(/title="([^\"]+\")/);
    if (title != null) {
    title = title[1].substr(0, title[1].length-1);
    for (j in attachments) {
       if (attachments[j].getName() == title) {
        inlineImages[title] = attachments[j].copyBlob();
        attachments.splice(j,1);
       }
    }
    var newImg = imgVars[i].replace(/src="[^\"]+\"/,"src=\"cid:"+title+"\"");
    emailTemplate = emailTemplate.replace(imgVars[i],newImg);
    }
  }
 }

 objects = getRowsData(dataSheet, dataRange);
 for (var i = 0; i < objects.length; ++i) {   
   var rowData = objects[i];
   if(rowData.emailSent != "EMAIL_SENT") {

     // Replace markers (for instance ${"First Name"}) with the 
     // corresponding value in a row object (for instance rowData.firstName).

     var emailText = fillInTemplateFromObject(emailTemplate, rowData);     
     var emailSubject = fillInTemplateFromObject(selectedTemplate.getSubject(), rowData);

     GmailApp.sendEmail(rowData.emailAddress, emailSubject, emailText,
                      {name: e.parameter.name, 
                       attachments: attachments, 
                       htmlBody: emailText, 
                       cc: cc, 
                       bcc: bcc, 
                       inlineImages: inlineImages});      
3

There are 3 answers

0
RobG On

Some comments:

> var array = { item1, item2, item3 };

That is syntactically incorrect, an array literal should be:

var array = [ item1, item2, item3 ];

[...]

> if (emailTemplate.search(/<\img/ != -1)) {

The backslash before img is unnecessary, the pattern should would be better with a trailing space and be case insensitive (since HTML is case insensitive and usually presented with tag names in capital letters), so /<img /i

> var imgVars = emailTemplate.match(/<img[^>]+>/g);

Parsing HTML with a regular expression is not a good idea, much better to convert the HTML to a document fragment and deal with that.

>  var imgVars = emailTemplate.match(/<img[^>]+>/g);

Note that String.prototype.match returns an array.

> for (i in imgVars) {

Using for..in with an array is not recommended for a number of reasons, such as the members may not be returned in any particular order (it may be different in different browsers) and for..in will return all enumerable properties of the array and its [[Prototype]] so if you are in a browser that has had Array.prototype modified by a "shim" or "monkey patch" then those properties will be enumerated too, so:

> var title = imgVars[i].match(/title="([^\"]+\")/);

may well be attempting to call match on a property whose value is a reference to a function and so throw an error. At the very least a hasOwnProperty test should be included, but better to just use a plain for loop.

> for (j in attachments) {

It seems attachments is an array too, so use a plain for loop here too for the same reasons as above. It's also not a good idea to use for..in (which may visit properties in an unexpected order) with splice, which infers you are expecting a particular order. Note that in some browsers, for..in will visit array members in the order they were added to the array, not in numeric order. Other browsers will always visit numeric properties in a particular order but not others.

Much of this will be a lot simpler if the HTML is converted to a document fragment, then DOM methods can be used to extract the img elements and access their properties to build an object. Then native methods can be used to convert the object to JSON if required.

2
Peter On

100% fidelity from a draft or canned response is totally do-able. This code snippet is from a working mail merge I extended to support inline images (both embedded blobs and external references) and attachments as well:

...
//selectedTemplate is a Gmail Message (draft/canned response) 
var emailTemplate = selectedTemplate.getBody(); 
var attachments = selectedTemplate.getAttachments();
var to = selectedTemplate.getTo();
var cc = selectedTemplate.getCc();
var bcc = Session.getActiveUser().getEmail();

if(emailTemplate.search(/<\img/ != -1)){  
    var inlineImages = {};
    var imgVars = emailTemplate.match(/<img[^>]+>/g);
    for(i in imgVars){
      var title = imgVars[i].match(/title="([^\"]+\")/);
      if (title) {
        title = title[1].substr(0, title[1].length-1);
        var titleEncoded = title.replace(/ /g,"-");
        for(j in attachments){
          if(attachments[j].getName() == title){
            inlineImages[titleEncoded] = attachments[j].copyBlob().setName(titleEncoded);
            attachments.splice(j,1);
          }
        }
        var newImg = imgVars[i].replace(/src="[^\"]+\"/,"src=\"cid:"+titleEncoded+"\"");
        emailTemplate = emailTemplate.replace(imgVars[i],newImg);
      }
    }
  }
...
GmailApp.sendEmail(....,
                  {attachments: attachments, ...,
                   inlineImages: inlineImages});

I have this working with domain users all day, every day. Hope this helps.

0
Julie On

Unfortunately, the inline images don't have a title. They have an alt=Inline Image 1, but that is not the same as the name of the attachment. The only items in the img tag are alt and src.

Given that, there doesn't seem to be any way to tie the inline image to the attachments other than to hope that the first inline image is the first attachment, etc.

I should note that I'm using the Gmail web interface to create the draft, and inserting images via the "Inserting Images" lab.