Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃憢馃徑 Are you using Morphism for a project or a library ? Please share the way you use it. #35

Open
emyann opened this issue Sep 26, 2018 · 15 comments

Comments

@emyann
Copy link
Member

emyann commented Sep 26, 2018

Last time, I was thinking about adding more real use cases to the documentation. According to npm, there is a certain amount of downloads, I was wondering how and for what do you use Morphism

Please share you usage or any relevant public link.

example: #33 (comment)

@emyann emyann self-assigned this Sep 26, 2018
@emyann emyann changed the title 馃憢馃徑 Are you using Morphism for a project or a library ? Please share how you use it. 馃憢馃徑 Are you using Morphism for a project or a library ? Please share the way you use it. Sep 26, 2018
@emyann emyann closed this as completed Oct 7, 2018
@okbriel
Copy link

okbriel commented Nov 11, 2018

Hello, I am using to convert Angular Reactive Forms to model types.

My form object contains

{
  doc: "123",
  nameNickname: "gvsouza"
}

My model is

class User {
  // nameNickname map to :1 of these two bellow, depending of the value of doc
  // if doc.length is > 3, maps to name else maps to nickname
  name?: string;
  nickname?: string;

  // doc maps to one of these two fields bellow (if doc.length>3 ? rg : ft)
  rg?: string;
  ft?: string;
}

For the input example I gave, I wanted:

{
   ft: "123",
   nickname: "gvsouza"
}

Still trying to figure out a way

@okbriel
Copy link

okbriel commented Nov 11, 2018

The way I solved is having a function that returns a schema depending on the doc value of the form.

fromSchema() {
    let schema = {};
    if (this.form.get('doc').value.length > 3) {
      schema = {
        rg: 'doc',
        name: 'nameNickname',
      };
    } else {
      schema = {
        ft: 'doc',
        nickname: 'nameNickname'
      };
    }

    return morphism(schema, this.form.value);
  }

I personally don't like it, I would rather have the schema not add the field if I don't return a value from a function.

const schema = {
      ft: (i, s, d) => {
        if (i.doc.length <= 3) {
          return i.doc;
        }
      },
      rg: (i, s, d) => {
        if (i.doc.length > 3) {
          return i.doc;
        }
      },
      nickname: (i, s, d) => {
        if (i.doc.length <= 3) {
          return i.nameNickname;
        }
      },
      name: (i, s, d) => {
        if (i.doc.length > 3) {
          return i.nameNickname;
        }
      },
    };

Don't know which one is worse...Is there another way?

@emyann emyann reopened this Nov 12, 2018
@emyann
Copy link
Member Author

emyann commented Nov 12, 2018

Hello @gvsouza, thank you for sharing your experience with Morphism !

I would prefer using the second approach too. You can make it a bit less verbose using ES6 destructuring

https://repl.it/@yrnd1/PhonyHeartfeltPattern

const input = {
  doc: "123",
  nameNickname: "gvsouza"
}

const schema = {
  ft: ({ doc }) => doc.length <= 3 ? doc : null,
  rg: ({ doc }) => doc.length > 3 ? doc: null,
  nickname: ({ doc, nameNickname }) => doc.length <= 3 ? nameNickname: null,
  name: ({ doc, nameNickname }) => doc.length > 3 ? nameNickname: null,
};

const output = morphism(schema, input)
// =>
// {
//   "ft": "123",
//   "rg": null,
//   "nickname": "gvsouza",
//   "name": null
// }

I would use the first approach if having 2 target models would make more sense, for example:

class UserWithNickname {
  nickname: string;
  ft: string;
}

class UserWithName {
  name: string;
  rg: string;
}

@okbriel
Copy link

okbriel commented Nov 13, 2018

The thing is, I didn't want the extra null properties. I 鉂わ笍 the project though

@emyann
Copy link
Member Author

emyann commented Nov 13, 2018

@gvsouza This is much appreciated! Don't hesitate to share it around you or ask questions whenever you need it. Thank you! 馃槂

@emyann emyann closed this as completed Jan 20, 2019
@davej
Copy link

davej commented May 27, 2019

Running into the same issue as @gvsouza. I wonder does a special string constant to remove a key make sense?

import { REMOVE_KEY } from 'morphism';

const schema = {
  ft: ({ doc }) => doc.length <= 3 ? doc : REMOVE_KEY,
  rg: ({ doc }) => doc.length > 3 ? doc: REMOVE_KEY,
  nickname: ({ doc, nameNickname }) => doc.length <= 3 ? nameNickname : REMOVE_KEY,
  name: ({ doc, nameNickname }) => doc.length > 3 ? nameNickname : REMOVE_KEY,
};

@emyann
Copy link
Member Author

emyann commented May 27, 2019

@davej I understand the point. What do you think if I add a new option to the schema allowing to strip null values and any other values as a post processing transformation ? Please look at this issue where I added an option to strip undefined values #52 (comment) and let me know what you think about it.

@davej
Copy link

davej commented May 28, 2019

Perfect, thanks Yann.

@aslaker
Copy link

aslaker commented Apr 30, 2020

Hi @emyann Fantastic library you have here.

I am using it to keep an internal business object that we have defined, in sync with a flattened out UI object that we are using for React application state. This way we can design an internal object that can work with multiple applications (and not care about any specific UI needs), and we can morph that object into a different UI state depending on the needs of the specific application.

Paired with the useEffect hook I am always able to keep a version of our business object in sync with the UI specific state. This makes it really easy to post the updated business object to the server without transforming on the fly.

Thanks again for being generous with your time and building this library. I look forward to using it more.

@emyann
Copy link
Member Author

emyann commented May 3, 2020

@aslaker Thank you for this feedback! 馃檹馃徑 So glad to hear how Morphism helps you with your use case, I would be happy if you're able to share a code snippet of how you use it with a useEffect hook.

Thanks!

@aslaker
Copy link

aslaker commented May 5, 2020

@emyann below is a snippet that shows how I am using it with useEffect (with some pseudo-ish code). This way the currentBusinessObject is always in a ready-to-post state whenever the user is done making changes. I have an additional file that defines the mapping (a lot of it is one to one, but some of them require a mapping function) The next step that I may take is raise the logic one level higher into my axios instance config, and use Morphism as middleware. Let me know if you have any questions, or suggestions for using Morphism in a better way.

const businessObjectResponse = await fetchComplexBusinessObject();

const [currentUIState, setCurrentUIState] = useState({})
const [currentBusinessObject, setCurrentBusinessObject] = useState({...businessObjectResponse})

useEffect(() => {
    let uiStateCopy = makeDeepClone(currentUIState);
    let updatedBusinessObject = morphism(UIToBusinessObjectSchema, uiStateCopy);
    setCurrentBusinessObject(updatedBusinessObject)
}, [currentUIState])

@emyann
Copy link
Member Author

emyann commented May 7, 2020

@aslaker Thanks for sharing! Do you think it would be valuable to have a react-morphism package that exposes a hook ?
Also I'm curious why you're doing a makeDeepClone before morphing towards your business object ?

@aslaker
Copy link

aslaker commented May 8, 2020

@emyann I think that a react-morphism package might have some merit. Especially if it can expose some sort of watch function or event. I'd have to think on that a bit more to see what problems a package like that could solve.

The makeDeepClone is in there because the uiState is a large somewhat nested object. It just uses lodash under the hood. I wasn't sure if morphism performs a deep copy during the transformation, so I wanted to make sure the original object remains immutable as it should be completely separate from the business object without any left over references.

@mdoesburg
Copy link

mdoesburg commented Jul 23, 2020

@emyann Thanks for creating Morphism! We are currently using it in a REST API which runs on AWS Lambda. The API is responsible for syncing data between 2 backends. The original backend was made for a website that was developed a while ago. The new backend (AWS) powers a React Native app made with Amplify.

The 2 backends use different databases. The app uses DynamoDB (NoSQL) and the original backend (made for the website) uses an SQL based database. Since the 2 databases are not 100% identical, some of the attribute names in the NoSQL tables can differentiate from the corresponding SQL table column name. Because of the mismatch in names, and sometimes data shape/structure, we use Morphism to transform the data.

Besides Morphism, we're using ajv for JSON validation. The reason we're using ajv on top of Morphism is because we need to be able to validate value types (string, int etc.), provide defaults for missing fields, and specify which fields are required.

Some questions I've gathered after using Morphism for a while:

1. Is there way to remove null and undefined keys with Morphism?

I saw how to remove undefined keys in your comment, but don't know how to achieve the same for null.

Use case:
The use case is related to DynamoDB. When we create items for the first time we don't have to store values that are null to save space and money. When we update items we do allow null as input, so we know if an attribute (== column in SQL) has to be deleted for a certain item (== row in SQL).

2. How do you transform a source but keep all other keys and values that weren't included in the target?

Use case:
I guess how I see this is like having to choose between using an allow list or a deny list. For example, if you are the only person that is allowed to go to some website you own, you could put your IP address on some allow list you've set up. You wouldn't use a deny list in this case, because you would have to add all the other IP addresses in the world to this deny list.

Let's say I have a large data structure and would like to only transform 2 keys. Preferably I would define a schema with only those 2 keys and still end up with all the other keys that weren't included in the schema in my output.

Here's an example:

const source = {
  id: 123,
  user_id: 1,
  another_field1: 'test',
  another_field2: 'test',
  another_field3: 'test',
  another_field4: 'test',
  another_field5: 'test',
};

const schema = {
  external_id: 'id',
  owner_id: 'user_id',
};

const result = morphism(schema, source);

I would like the result to end up like this:

{
  external_id: 123,
  owner_id: 1,
  another_field1: 'test',
  another_field2: 'test',
  another_field3: 'test',
  another_field4: 'test',
  another_field5: 'test',
}

I know I can use the spread operator to achieve the same thing, but I would still have to ignore/delete the keys that did get morphed:

const source = {
  id: 123,
  user_id: 1,
  another_field1: 'test',
  another_field2: 'test',
  another_field3: 'test',
  another_field4: 'test',
  another_field5: 'test',
};

const schema = {
  external_id: 'id',
  owner_id: 'user_id',
};

const result = morphism(schema, source);

// Still contains id and user_id
const merged = { ...source , ...result };

// Ignore id and user_id from merged
// The variable final is the result we want
const { id, user_id, ...final } = merged;

One final note: Our data structures can be relatively deep. Sometimes 3 or 4 nested objects and/or arrays or combinations. Would it be possible to add more examples for nested data, or is it recommended to write different schemas for each level? For example, imagine a trip object which has the following sub objects, each with data to transform and validate:

trip -> destination -> hotels -> rooms

Taking the example above, sometimes a certain sub object isn't present:

trip -> destination -> hotels -> rooms

I couldn't really find out from the docs how to handle such cases.

@voda
Copy link

voda commented Oct 27, 2020

Hi, thanks for this library, used it for the first time. I really like it works well with TypeScript.

One issue which required some post-processing is partial objects:

interface Source {
  foo: any;
  bar: any;
  baz: any;
}
interface Dest {
  myFoo: any;
  myBar: any;
  myBaz: any;
}
const schema = {
  myFoo: 'foo',
  myBar: 'bar',
  myBaz: 'baz'
};

And I want to map Partial<Source> to Partial<Dest>. Now even if some properties are missing in the source object they will be present in the resulting object.

const source = { foo: 'my-foo-value', bar: undefined };
const result = morphism(createSchema<Partial<Dest>, Partial<Source>>(schema), source);
console.log(result);
// output
{
  myFoo: 'my-foo-value',
  myBar: undefined,
  myBaz: undefined,
}

But I would like only:

{
  myFoo: 'my-foo-value',
  myBar: undefined,
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants