morphism icon indicating copy to clipboard operation
morphism copied to clipboard

Morphism over a array ignore element

Open SamuelColacchia opened this issue 5 years ago • 2 comments

Question

I have been messing around with morphism for the past few weeks and thus far it has been a delight to use!

While using it to morph a array of object I encountered a scenario where I would like to remove a object from the array. Is this possible to do in a schema ?

Information

Schema

createSchema({
  sku: orderItem => {
    if (/* This sku looks good return it */) {
        return orderItem.sku;
    } else if (/** This sku is bad */) {
        // we should remove this object
    } else {
        // Something else
    }
  },
  // other data
});

Data

[
    {
        sku: 'goodsku',
        // other data
    },
    {
        sku: 'badsku'
        // other data
    }
]

Current Result

We set the value of sku to something like null and then loop through the array and remove all object that contain a sku: null

Current Result Data

[
    {
        sku: 'goodsku',
        // other data
    },
    {
        sku: 'null'
        // other data
    }
]

Ideal Result

Perhaps export a enum from morphism that signals this object should be removed. Perhaps this could be apart of #85?

Ideal Result Data

[
    {
        sku: 'goodsku',
        // other data
    }
]

Version

morphism: 1.12.0

SamuelColacchia avatar Aug 08 '19 21:08 SamuelColacchia

Hi @SamuelColacchia, so happy you're having a good time using Morphism! 🎉 Thank you for this detailed case 👌🏽

Do you want to strip a specific property, or the entire object ?

As you already mentioned this is not actually possible to skip a complete object during the transformation. https://github.com/nobrainr/morphism/issues/85 will be about to strip a specific key of an object but your suggestion looks a lot like https://github.com/nobrainr/morphism/issues/35#issuecomment-496270377.

I was thinking about some workarounds and ended up with this solution https://repl.it/@yrnd1/morphism-strip-key

interface Source {
  sku: string
}

interface Target {
  sku?: string | null
}
const schema = createSchema<Target, Source>({
  sku: orderItem => {
    if (orderItem.sku === 'good') {
      return orderItem.sku;
    } else if (orderItem.sku === 'bad') {
      return; // returning undefined will skip this property from the final object
    } else {
      return null
    }
  }
}, { undefinedValues: { strip: true } });

const result = morphism(schema, [{ sku: 'good' }, { sku: 'bad' }])
// => [ { sku: 'good' }, {} ]

Using the options of the schema to strip the undefined values let you skip the property from the object, but you'll still end up with an object containing the rest of the data.

I guess your approach is actually to apply a filter after the transformation has been done in order to filter out the objects you don't want ?

If the workaround above along with a filter does not answer to your question, I could think about 2 solutions:

1. Using a constant signaling the object / key should be stripped When Morphism will encounter the value REMOVE_OBJECT in any of the key of the final object it will strip the object.

import { REMOVE_OBJECT } from 'morphism';

createSchema({
  sku: orderItem => {
    if (/* This sku looks good return it */) {
        return orderItem.sku;
    } else if (/** This sku is bad */) {
        return REMOVE_OBJECT
    } else {
        // Something else
    }
  },
  // other data
});

2. Using a post processing predicate When Morphism matches a specific predicate against the final object, it will strip it.

createSchema({
  sku: orderItem => {
    if (/* This sku looks good return it */) {
        return orderItem.sku;
    } else if (/** This sku is bad */) {
        return null
    } else {
        // Something else
    }
  }
}, { stripObject: (target) => target.sku === null  });

I kinda feel the second solution is more flexible since it gives the consumer the ability to define on which criteria an object is stripped, but I'm curious about the solution you would pick ? 🙂

emyann avatar Aug 09 '19 03:08 emyann

Hi @emyann, Thanks for the detailed post and examples!

Do you want to strip a specific property, or the entire object ?

Remove the entire object

I guess your approach is actually to apply a filter after the transformation has been done in order to filter out the objects you don't want ?

Yes, exactly! I have been thinking of helper functions to write to each this goal. My current idea is fairly simple and i think very crude, a recursive function which loops through a given object and if it encounters a REMOVE_OBJECT const value then it removes the current object. If it is in a array it is removed from the array, if it encounters REMOVE_OBJECT in a root object it returns undefined.

I kinda feel the second solution is more flexible since it gives the consumer the ability to define on which criteria an object is stripped, but I'm curious about the solution you would pick ? 🙂

Your 2nd ideal seems brilliant! It provides the most flexibility and it overall very robust. I may have to write a helper function to use with my schemas following that idea.

So to talk about your 2nd idea in practice.

It would be a search predicate which would?

Loop through all values of a given object, going how deep?

(First layer, deep, configurable depth.)

If the predicate returns true then remove the whole object, if in array then removes from array, if in root object does what?

(returns undefined, returns configurable value, throws error.)

Side Thought

This maybe something that is better suited to a seperate issue but what are your thoughts on morphism actions effect other morphism actions or to say it another way action dependency? For example:

createSchema({
  group: async target => {
    const isGroupSpecial: boolean = await checkIfGroupIsSpecial(target.group);
    if (isGroupSpecial) {
      // group is special 😄
      return target.group;
    } else {
      // group is no special 😦
      return 'notspecial group';
    }
  },
  dependsOnGroupValue: target => {
    /**
     * I depend on the result of group I have a few options:
     * 1: I repeat the code that ran in group so i get the same result
     *      - Duplicate code, make function instead
     *      - multiple async calls, maybe cache the data some how?
     * 2: I somehow access the value of group, is this even possible?
     * 3: My value gets set in a 2nd call to morphism
     */
  };
});

Possible solutions

  1. Create a function that you call multiple times for dependent values, i currently do this by create a wrapper class around each schema that declares all the functions a schema can use.
  2. Split the morphing process into multiple stages. So initially a base level schema that depends on nothing, then the next level which depends on every before it and so on.
  3. Define a dependency system in morphism which would allow a action to wait for the result of another action and then proceed.

Solution one and two would require no change to morphism just a maybe creating a examples folder saying hey here is how you do dependent actions. Solution 3 seems to be most robust but also may require alot a work to implement.

What do you think @emyann?

SamuelColacchia avatar Aug 12 '19 21:08 SamuelColacchia