typescriptobjectfunctional-programmingramda.jsmediator

Return object that doesn't contain any key or value of another object in Ramda


New to ramda, building a mediator class I have an object describing registered (authorized) channels/messages. These channels are meant to be unique, both in their keys and values. At registering time, they are passed like this:

enum MyMessages {
 FirstMessage = 'first:message',
 SecondMessage = 'second:message'
};

mediator.addChannels(MyMessages); // With a validator to keep them unique.

To remove channels I call mediator.removeChannels(MyMessages);. The underlying imperative implementation works well:

// Makes a shallow clone of the existing channels.
let result = { ...existingChannels };
// Iterates the channels to remove.
for (const [key, value] of Object.entries(channelsToRemove)) {
  // Keys match.
  if (Object.keys(result).includes(key)) {
    delete result[key];
  }
  // Values match.
  if (Object.values(result).includes(value)) {
    // Finds the key for the given value.
    const k = Object.keys(result).find((a: string) => result[a] === value);
    if (k) {
      delete result[k];
    }
  }
  // Channels to remove value matches an existing key.
  if (Object.keys(result).includes(value as string)) {
    delete result[value as string];
  }
  // Channels to remove key matches an existing value.
  if (Object.values(result).includes(key)) {
    const k = Object.keys(result).find((a: string) => result[a] === key);
    if (k) {
      delete result[k];
    }
  }
}
return result;

It's a bit naive and could be refactored, but at the end, I get my channels object without the keys / values that were removed.

I would like to replace this with ramda functions.

I'm able to get overlapping keys with something like

R.without(R.keys(channelsToRemove), R.keys(existingChannels))

But I can't understand how I can easily get the final object (e.g. without the second object's keys or values).

const obj1 = {
  foo: 'bar',
  baz: 'sum'
}

const obj2 = {
  baz: 'hop'
}

const obj3 = {
  sum: 'ack'
}

As a result, I'd like this to happen:


Solution

  • This Ramda version looks to me like it does what you want:

    const allDiff = (x, y) => fromPairs (filter (
      none (flip (includes) ([...keys (y), ... map (String) (values (y) )]))
    ) (toPairs (x)))
    
    const obj1 = {foo: 'bar', baz: 'sum'}
    const obj2 = {baz: 'hop'}
    const obj3 = {sum: 'ack'}
    
    console .log (allDiff (obj1, obj2))
    console .log (allDiff (obj1, obj3))
    console .log (allDiff (obj2, obj3))
    <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
    <script>const {fromPairs, filter, none, flip, includes, keys, values, map, toPairs} = R</script>

    We use toPairs -> filter -> fromPairs to take apart our first object, filter the resulting key-value pairs, and then put it back together. The filter checks that neither element (none) of our key-value pair is included in (flip (includes)) the list of element including the keys or values of our second object. We add map (String) over the values in order to cover your value as string. (I don't know TS well, but I'm guessing that this is equivalent.)

    It's not clear to me, though, how much of an improvement this version is over a vanilla JS version:

    const allDiff = (x, y, 
      props = [...Object .keys(y), ... Object .values(y) .map (String)], 
      fn = (v) => props .includes (v)
    ) => Object .fromEntries (
      Object .entries (x) .filter (([k, v]) => !(fn(k) || fn (v)))
    )
    

    const allDiff = (x, y, 
      props = [...Object .keys(y), ... Object .values(y) .map (String)], 
      fn = (v) => props .includes (v)
    ) => Object .fromEntries (
      Object .entries (x) .filter (([k, v]) => !(fn(k) || fn (v)))
    )
    
    
    const obj1 = {foo: 'bar', baz: 'sum'}
    const obj2 = {baz: 'hop'}
    const obj3 = {sum: 'ack'}
    
    console .log (allDiff (obj1, obj2))
    console .log (allDiff (obj1, obj3))
    console .log (allDiff (obj2, obj3))

    If you're already using Ramda, I think the Ramda one is a bit cleaner. But I don't think it's enough of an advantage that I would add Ramda to a codebase just for that.

    While I'm sure we could make this entirely point-free with enough effort, I'm guessing that doing so would make it much less readable.