Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - favfork/morphism: ⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js. · GitHub
Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - favfork/morphism: ⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js. · GitHub
Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - favfork/morphism: ⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js. · GitHub
Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - favfork/morphism: ⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js. · GitHub
Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - favfork/morphism: ⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js. · GitHub
Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - favfork/morphism: ⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js. · GitHub
Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - favfork/morphism: ⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js. · GitHub
Skip to content

Repository files navigation

Morphism

Financial Contributors on Open Collectivenpmnpm bundle size (minified)npmCircleCI (all branches)DepsGreenkeeper badge

In many fields of mathematics, morphism refers to a structure-preserving map from one mathematical structure to another. A morphism f with source X and target Y is written f : X → Y. Thus a morphism is represented by an arrow from its source to its target.

https://en.wikipedia.org/wiki/Morphism

  • ⚛️ Write your schema once, Transform your data everywhere
  • 0️⃣ Zero dependencies
  • 💪🏽 Typescript Support

Getting started

Installation

npm install --save morphism

or in the browser

<scriptsrc="https://unpkg.com/morphism/dist/morphism.js"></script><script>const{ morphism, createSchema }=Morphism</script>

Usage

The entry point of a morphism is the schema. The keys represent the shape of your target object, and the values represents one of the several ways to access the properties of the incoming source.

constschema={targetProperty: 'sourceProperty'};

Then use the morphism function along with the schema to transform any source to your desired target

import{morphism}from'morphism';constsource={_firstName: 'Mirza'};constschema={name: '_firstName'};morphism(schema,source);{"name": "Mirza"}

You may specify properties deep within the source object to be copied to your desired target by using dot notation in the mapping value. This is one of the actions available to transform the source data

constschema={foo: 'deep.foo',bar: {baz: 'deep.foo'}};constsource={deep: {foo: 'value'}};morphism(schema,source);{"foo": "value","bar": {"baz": "value"}}

One important rule of Morphism is that it will always return a result respecting the dimension of the source data. If the source data is an array, morphism will outputs an array, if the source data is an object you'll have an object

constschema={foo: 'bar'};// The source is a single objectconstobject={bar: 'value'};morphism(schema,object);{"foo": "value"}// The source is a collection of objectsconstmultipleObjects=[{bar: 'value'}];morphism(schema,multipleObjects);[{"foo": "value"}]

Example (TypeScript)

import{morphism,StrictSchema}from'morphism';// What we haveinterfaceSource{ugly_field: string;}// What we wantinterfaceDestination{field: string;}constsource: Source={ugly_field: 'field value'};// Destination and Source types are optionalmorphism<StrictSchema<Destination,Source>>({field: 'ugly_field'},source);// => {field: "field value"}// Orconstsources=[source];constschema: StrictSchema<Destination,Source>={field: 'ugly_field'};morphism(schema,sources);// => [{field: "field value"}]

▶️Test with Repl.it

Motivation

We live in a era where we deal with mutiple data contracts coming from several sources (Rest API, Services, Raw JSON...). When it comes to transform multiple data contracts to match with your domain objects, it's common to create your objects with Object.assign, new Object(sourceProperty1, sourceProperty2) or by simply assigning each source properties to your destination. This can leads you to have your business logic spread all over the place.

Morphism allows you to keep this business logic centralized and brings you a top-down view of your data transformation. When a contract change occurs, it helps to track the bug since you just need to refer to your schema

TypeScript integration

When you type your schema, this library will require you to specify each transformation for your required fields.

schema

schema-required-fields

This library uses TypeScript extensively. The target type will be inferred from the defined schema.

inferred field type

When using an ActionFunction the input type is also inferred to enforce your transformations

typed action function

See below the different options you have for the schema.

Docs

📚 API documentation

Morphism comes with 3 artifacts to achieve your transformations:

1. The Schema

A schema is an object-preserving map from one data structure to another.

The keys of the schema match the desired destination structure. Each value corresponds to an Action applied by Morphism when iterating over the input data.

Schema actions

You can use 4 kind of values for the keys of your schema:

  • ActionString: A string that allows to perform a projection from a property
  • ActionSelector: An Object that allows to perform a function over a source property's value
  • ActionFunction: A Function that allows to perform a function over source property
  • ActionAggregator: An Array of Strings that allows to perform a function over source property

Schema Example

import{morphism}from'morphism';constinput={foo: {baz: 'value1'}};constschema={bar: 'foo',// ActionString: Allows to perform a projection from a propertyqux: ['foo','foo.baz'],// ActionAggregator: Allows to aggregate multiple propertiesquux: (iteratee,source,destination)=>{// ActionFunction: Allows to perform a function over source propertyreturniteratee.foo;},corge: {// ActionSelector: Allows to perform a function over a source property's valuepath: 'foo.baz',fn: (propertyValue,source)=>{returnpropertyValue;}}};morphism(schema,input);// {// "bar": {// "baz": "value1"// },// "qux": {// "foo": {// "baz": "value1"// }// },// "quux": {// "baz": "value1"// },// "corge": "value1"// }

▶️Test with Repl.it

More Schema examples

📚 Schema Docs

1.1 Using a strict Schema

You might want to enforce the keys provided in your schema using Typescript. This is possible using a StrictSchema. Doing so will require to map every field of the Target type provided.

interfaceIFoo{foo: string;bar: number;}constschema: StrictSchema<IFoo>={foo: 'qux',bar: ()=>'test'};constsource={qux: 'foo'};consttarget=morphism(schema,source);// {// "foo": "qux",// "bar": "test"// }

2. Morphism as Currying Function

The simplest way to use morphism is to import the currying function:

import{morphism}from'morphism';

morphism either outputs a mapping function or the transformed data depending on the usage:

API

morphism(schema: Schema,items?: any,type?: any): any

📚 Currying Function Docs

Currying Function Example

// Outputs a function when only a schema is providedconstfn=morphism(schema);constresult=fn(data);// Outputs the transformed data when a schema and the source data are providedconstresult=morphism(schema,data);// Outputs the transformed data as an ES6 Class Object when a schema, the source data and an ES6 Class are providedconstresult=morphism(schema,data,Foo);// => Items in result are instance of Foo

3. Morphism Function as Decorators

You can also use Function Decorators on your method or functions to transform the return value using Morphism:

toJsObject Decorator

import{toJSObject}from'morphism';classService{
@toJSObject({foo: currentItem=>currentItem.foo,baz: 'bar.baz'})asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will return// =>// {// foo: 'fooValue',// baz: 'bazValue'// }--------------------------------// Using Typescript will enforce the key from the target to be requiredclassTarget{a: string=null;b: string=null;}classService{// By Using <Target>, Mapping for Properties `a` and `b` will be required
@toJSObject<Target>({a: currentItem=>currentItem.foo,b: 'bar.baz'})fetch();}

toClassObject Decorator

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@toClassObject(schema,Target)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}}// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

morph Decorator

Utility decorator wrapping toClassObject and toJSObject decorators

import{toClassObject}from'morphism';classTarget{foo=null;bar=null;}constschema={foo: currentItem=>currentItem.foo,baz: 'bar.baz'};classService{
@morph(schema)asyncfetch(){constresponse=awaitfetch('https://api.com');returnresponse.json();// =>// {// foo: 'fooValue'// bar: {// baz: 'bazValue'// }// };}
@morph(schema,Target)asyncfetch2(){constresponse=awaitfetch('https://api.com');returnresponse.json();}}// await service.fetch() will be// =>// {// foo: 'fooValue',// baz: 'bazValue'// }// await service.fetch() will be instanceof Target// =>// Target {// foo: 'fooValue',// baz: 'bazValue'// }

4. Default export: Morphism object

Morphism comes along with an internal registry you can use to save your schema attached to a specific ES6 Class.

In order to use the registry, you might want to use the default export:

importMorphismfrom'morphism';

All features available with the currying function are also available when using the plain object plus the internal registry:

// Currying FunctionMorphism(schema: Schema,items?: any,type?: any): any// Registry APIMorphism.register(type: any,schema?: Schema);Morphism.map(type: any,data?: any);Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);Morphism.deleteMapper(type);Morphism.mappers

🔗 Registry API Documentation

More Schema examples

Flattening or Projection

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'baz',bar: ['bar','foo'],baz: {qux: 'bazqux'}};constschema={foo: 'foo',// Simple Projectionbazqux: 'baz.qux'// Grab a value from a deep path};morphism(schema,source);//=> { foo: 'baz', bazqux: 'bazqux' }

▶️Test with Repl.it

Function over a source property's value

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={barqux: {path: 'foo.bar',fn: value=>`${value}qux`// Apply a function over the source property's value}};morphism(schema,source);//=> { barqux: 'barqux' }

▶️Test with Repl.it

Function over a source property

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: {bar: 'bar'}};letschema={bar: iteratee=>{// Apply a function over the source properyreturniteratee.foo.bar;}};morphism(schema,source);//=> { bar: 'bar' }

▶️Test with Repl.it

Properties Aggregation

import{morphism}from'morphism';// Source data coming from an API.constsource={foo: 'foo',bar: 'bar'};letschema={fooAndBar: ['foo','bar']// Grab these properties into fooAndBar};morphism(schema,source);//=> { fooAndBar: { foo: 'foo', bar: 'bar' }}

▶️Test with Repl.it

Registry API

📚 Registry API Documentation

Register

Register a mapper for a specific type. The schema is optional.

Morphism.register(type: any,schema?: Schema);

Map

Map a collection of objects to the specified type

Morphism.map(type: any,data?: any);

Get or Set an existing mapper configuration

Morphism.setMapper(type: any,schema: Schema);Morphism.getMapper(type);

Delete a registered mapper

Morphism.deleteMapper(type);

List registered mappers

Morphism.mappers;

Contribution

Similar Projects

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

License

MIT © Yann Renaudin

About

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages