2

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.

dzh
  • 286
  • 1
  • 19
  • What is the data-type and typical value of `data` and `i`? Are you using Promises, Observables/RxJS, or something else? – Dai Jun 04 '21 at 02:52
  • It's a little hard to tell what you're asking due to what is probably a bunch of unnecessary code. Are you just after a potentially shorter version of `id ? "/servlet/servlet.FileDownload?file=" + id : undefined`? – Phil Jun 04 '21 at 02:53
  • Object with multiple props – dzh Jun 04 '21 at 02:53
  • 1
    Your current code looks quite reasonable to me – CertainPerformance Jun 04 '21 at 02:54
  • 2
    @CertainPerformance thanks. Is CodeGolf more suitable forum? – dzh Jun 04 '21 at 02:55
  • @Phil added further explanation. – dzh Jun 04 '21 at 02:56
  • If you are not getting your undefined, you got to check the 'id' variable. Make sure id is a falsy – Isaac Khoo Jun 04 '21 at 02:56
  • Does this answer your question? [Looking for shorthand to insert a variable into object if not null](https://stackoverflow.com/questions/64774661/looking-for-shorthand-to-insert-a-variable-into-object-if-not-null) – Phil Jun 04 '21 at 02:58

4 Answers4

2

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.

Dai
  • 141,631
  • 28
  • 261
  • 374
1

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,
}));
Mike Martin
  • 3,850
  • 1
  • 15
  • 18
0

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 
     })
  );
Isaac Khoo
  • 525
  • 3
  • 9
  • This will result in the `image` property being defined, but having a nullish value when `i.Attachments?.[0]?.Id` is empty; whereas my understanding of the OP was that the `image` property should be omitted if empty/nullish. – Dai Jun 04 '21 at 03:01
  • FYI this is 29 characters longer than OP's code – Phil Jun 04 '21 at 03:02
  • @Dai no, OP has `undefined` as the fallback value for `image`. Unless `i.Attachments?.[0]` has an `Id` property that is _falsy_ but not `undefined`, this code will produce the same result – Phil Jun 04 '21 at 03:03
  • `i.Attachments?.[0]?.Id` could be an expensive operation (imagine `[].find()` instead) so I only want execute it once. – dzh Jun 04 '21 at 03:05
  • 1
    @dzh Why would that be "expensive"? It's just a dereference chain. It would only be expensive if the objects being passed-around have non-trivial property-getters, which is very unlikely (if not _impossible_) if these are just DTOs from JSON. – Dai Jun 04 '21 at 03:06
0

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 }) );
Dai
  • 141,631
  • 28
  • 261
  • 374
  • Yep had this idea as well, but IMO solution in question is faster and easier to understand – dzh Jun 04 '21 at 03:07
  • @dzh Regarding performance, your "opinion" is irrelevant, what matters is actual benchmarks :) I'll be happy to profile my answer if you can provide us with raw data to benchmark against. – Dai Jun 04 '21 at 03:08
  • @Dai I imagine OP is referring to their _O(n)_ solution vs your _2O(n)_ one. The difference in real time is probably negligible – Phil Jun 04 '21 at 03:10
  • No doubt it won't really matter in practice. Readability will so my opinion still stands :) – dzh Jun 04 '21 at 03:10
  • @Phil I've removed the initial spread-operator. The time-complexity of my solution is still `O(n)` though - and note that the spread-operator in JavaScript _can_ be a `O(1)` operation if the engine uses deferred evaluation of merged objects, or even uses a linked-list structure instead of naively copying hashmap entries. – Dai Jun 04 '21 at 03:15
  • You have two map operations over the same data set, thus _2O(n)_. FYI, I wasn't the one who pointed out the `id` property thing but saw that you fixed it. – Phil Jun 04 '21 at 03:21
  • 1
    @Phil `2O(n)` isn't correct notation - and using `map` twice in succession does not necessarily result in two naïve iterations. There's a lot of crazy but valid optimizations in modern JS engines (source: I used to work on Chakra) – Dai Jun 04 '21 at 03:21