nobrainr / morphism

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.
https://morphism-playground.now.sh/
MIT License
487 stars 23 forks source link

A little help with flattening ? #165

Closed iangregsondev closed 4 years ago

iangregsondev commented 4 years ago

Hi,

I wonder if you can help.

I have a Array of items but wish to return it flattened into items:, but it runs it multiple times.. I have placed a stackblitz at the end. I think I need to flatten but i didn't see an example.

The demo recreates what I am trying to do in production

// I get the following 
/*
[
   {
      "items":{
         "id":"123",
         "name":"CompanyA"
      }
   },
   {
      "items":{
         "id":"456",
         "name":"CompanyB"
      }
   },
   {
      "items":{
         "id":"2424",
         "name":"CompanyC"
      }
   },
   {
      "items":{
         "id":"111",
         "name":"CompanyD"
      }
   }
]
*/

// but expect

/*

{
   "items":[
      {
         "id":"123",
         "name":"CompanyA"
      },
      {
         "id":"456",
         "name":"CompanyB"
      },
      {
         "id":"2424",
         "name":"CompanyC"
      },
      {
         "id":"111",
         "name":"CompanyD"
      }
   ]
}
*/

I have created a stackblitz here that shows the issue

https://stackblitz.com/edit/morphism-single-source-to-complex-destination-1xms6b

Any ideas what I am doing wrong ?

If it helps, i am using Typescript but I doubt it makes a difference ?

iangregsondev commented 4 years ago

I should have mentioned (although its viewable in the stackblitz) - this is my source

const source = [
      {
        "id": "123",
        "name": "CompanyA"
      },
      {
        "id": "456",
        "name": "CompanyB"
      },
      {
        "id": "2424",
        "name": "CompanyC"
      },
      {
        "id": "111",
        "name": "CompanyD"
      }

    ]

and my destination type is Package

interface Item {
  name: string 
  id: string 
}

interface Package {
  items: Item[],
}
iangregsondev commented 4 years ago

I think where the problem is, of course I am not doing it correctly. Is the following

const myPackage: StrictSchema<Package, EmbeddedRecord[]> = {
items: a => morphism(item,a)
}

So I am passing the EmbeddedRecordp[] which is an array, so the conversion happens for each item in the array.

So for each item, a mapping is created for the whole array.

I can't really change the types / array that i am passing it BUT

if I do wrap the array inside of a plain object then I get the result. ie.

const tmp = {
  elements: EmbeddedRecord[] 
}
iangregsondev commented 4 years ago

Hi,

Well i managed to do it but I am not sure if its correct, I have commented inline

const myPackage: Schema<Package, EmbeddedRecord[]> = {  // Had to change StrictSchema to Schema
items: a => morphism(item,a)
}

const result2 = morphism(myPackage, [source]) // add to wrap source in []

Leaving it as strictSchema it gives types errors,

Could you explain a little more the difference between StrictSchema and Schema and when i would use one over the other?

The docs say that strictSchema means you have to map all values on the target but I am right ? which in my case is "items"

emyann commented 4 years ago

Hi @iangregsondev!

Instead of morphing towards the Package type, I would create a morph from EmbeddedRecord to Item and then add it to a Package object. Please see the example here: https://repl.it/@yrnd1/RequiredColorfulQuote

import { morphism, createSchema } from 'morphism';

interface EmbeddedRecord {
  id: string
  name: string
}

interface Item {
  name: string
  id: string
}

interface Package {
  items: Item[]
}

const toItem = morphism(createSchema<Item, EmbeddedRecord>({
  id: ({ id }) => id,
  name: ({ name }) => name
}))

const source = [
  {
    "id": "123",
    "name": "CompanyA"
  },
  {
    "id": "456",
    "name": "CompanyB"
  },
  {
    "id": "2424",
    "name": "CompanyC"
  },
  {
    "id": "111",
    "name": "CompanyD"
  }

]

const pkg: Package = {
  items: toItem(source)
}
console.log('pkg', JSON.stringify(pkg, null, 2))

// =>
/**
pkg {
  "items": [
    {
      "id": "123",
      "name": "CompanyA"
    },    {
      "id": "456",
      "name": "CompanyB"
    },
    {
      "id": "2424",
      "name": "CompanyC"
    },
    {
      "id": "111",
      "name": "CompanyD"
    }
  ]
}
**/

Is it what you're looking for ?

iangregsondev commented 4 years ago

Thanks, yes this works, i was just hoping to be pass in an array and have it flattened.

The above example was a simple case and luckily my real example isn't too much of a difference.

But the package is being created manually rather than going through morphism.

emyann commented 4 years ago

Ok yes I see what you tried :) You could also achieve it by using the example above: https://repl.it/@yrnd1/RequiredColorfulQuote-1?lite=&classroom_template=&outputonly=&fileName=

I just find it a little less explicit

import { morphism, createSchema } from 'morphism';

interface EmbeddedRecord {
  id: string
  name: string
}

interface Item {
  name: string
  id: string
}

interface Package {
  items: Item[]
}

const toItem = morphism(createSchema<Item, EmbeddedRecord>({
  id: ({ id }) => id,
  name: ({ name }) => name
}))

const toPackage = morphism(createSchema<Package,EmbeddedRecord[]>({
  items: (source)=> toItem(source)
}))

const source = [
  {
    "id": "123",
    "name": "CompanyA"
  },
  {
    "id": "456",
    "name": "CompanyB"
  },
  {
    "id": "2424",
    "name": "CompanyC"
  },
  {
    "id": "111",
    "name": "CompanyD"
  }

]

const pkg= toPackage([source])
console.log('pkg', JSON.stringify(pkg[0], null, 2))

// =>
/**

pkg {
  "items": [
    {
      "id": "123",
      "name": "CompanyA"
    },
    {
      "id": "456",
      "name": "CompanyB"
    },
    {
      "id": "2424",
      "name": "CompanyC"
    },
    {
      "id": "111",
      "name": "CompanyD"
    }
  ]
}

**/
iangregsondev commented 4 years ago

Thank you! One last question, if I may :-)

I was using strict schema before but it was causing me issues with the above-mentioned solution. I had to switch to a normal schema.

It states that a strict schema ensures that you map all items on the destination. I did this but it did not work.

I presume the best way is to use a schema vs strict schema.

I still couldn't fully understand the difference after reading the documentation because according to the docs - that is not what was happening to me :-) Maybe I should try again.

Is it recommended just to use a schema?

Thanks again for all your help,

emyann commented 4 years ago

Here are the type definitions for Schema and StrictSchema: https://github.com/nobrainr/morphism/blob/08434578d780d7f88ac676cc958b2190ff323659/src/types.ts#L35-L52

Notice the ? in [destinationProperty in keyof Target]? of the Schema type. What it basically means is that when using a Schema the fields are optional compared to a StrictSchema where it's mandatory to define every field's mapping.

So it depends on what you want to achieve, but I would recommend if you want to ensure all the fields are mapped to use a StrictSchema and the simplest way to do it is to use the helper createSchema<Target, Source>()

My pleasure :)

iangregsondev commented 4 years ago

Thank you. I will close this now as i have everything working now.