Conditional concatenation in modern JavaScript

Viewed 411

I'm looking for a neat way to conditionally concatenate strings.

See:

const something = response.data
  .map(i => {
    const id = i.Attachments?.[0]?.Id
    return { image: id ? "/servlet/servlet.FileDownload?file=" + id : undefined, ...i }
  })

I'd love to inline this, but can't find a way. Using null coelescing op like ('something' + foo) ?? undefined is never resolved to undefined.

Edit: I'd like to minimise this to something like .map(i => ({image: "/servlet/servlet.FileDownload?file=" + i.Attachments?.[0]?.Id, ...i})) but image to be empty if there's no Id. I hope there's a neat trick somewhere.

4 Answers

One of the pain points in most programming languages today is the inability - or difficulty - in expressing conditional string formatting operations (I wonder if this is a plot by language-designers to discourage us from using strings, after-all, strings are expensive and a PITA to manage without a GC). But as this is JavaScript, there's always the nuclear option: extending String.prototype:

String.prototype.p = a => this ? (a+this) : undefined;

Which means your code can retain its original form but be transformed to:

const something = response.data
  .map(i => {
    return { image: (i.Attachments?.[0]?.Id||'').p("/servlet/servlet.FileDownload?file="), ...i }
  })

And then shortened again to:

const something = response.data
  .map(i => ({image: (i.Attachments?.[0]?.Id||'').p("/servlet/servlet.FileDownload?file="), ...i }))

UPDATE: Now that I've slept on it, here's the IIFE version:

const something = response.data
  .map(i => ({image: (s=>s?("/servlet/servlet.FileDownload?file="+s):undefined)(i.Attachments?.[0]?.Id), ...i }))

And shrink again once more for good luck:

const something = response.data.map(i=>({image:(s=>s?("/servlet/servlet.FileDownload?file="+s):void 0)(i.Attachments?.[0]?.Id),...i}))

That's 135 characters total.

I think I see what you're looking for. You want to be able to reuse a complex expression inline without converting an expression-style arrow-function into a statement-style arrow function. You can use expression assignment in those cases.

const answer = (x=complex_expression?.[0]?.Id, x && "long/string.With/stuff"+x);

Here's an example using that:

const something = response.data.map(i => ({
  image: (id=i.Attachments?.[0]?.Id, id && "/servlet/servlet.FileDownload?file=" + id),
  ...i,
}));

And if you don't want the image key to be null, you can do this:

const something = response.data.map(i => ({
  ...(id=i.Attachments?.[0]?.Id, id && { image: "/servlet/servlet.FileDownload?file=" + id}),
  ...i,
}));

Is this what you are looking for?

const something = response.data
  .map(i => ({ 
     image: i.Attachments?.[0]?.Id && "/servlet/servlet.FileDownload?file=" + i.Attachments?.[0]?.Id,          
     ...i 
     })
  );

You could use an IIFE, but then we're getting silly. Alternatively, just another map layer does the trick:

const urlPrefix = "/servlet/servlet.FileDownload?file=";

const something = response.data
    .map(i => ({ id: i.Attachments?.[0]?.Id, i }) )
    .map(t => ({ image: t.id ? (urlPrefix + t.id) : undefined, ...t.i }) );
Related