parakeet-mapper
v3.0.0
Published
Simple data converter
Downloads
1,701
Readme
Parakeet Mapper
Simple data conversion library
npm install --save parakeet-mapper
For more options see installation
What is this?
It's a small collection of utility functions and types that help with mapping (transforming) data.
You can find it useful if:
- You work with complex data objects and tired of it
- You need to quickly and efficiently transform objects or tuples regularly
- Your data models need additional functionality
- You need a type-safe way of initializing a class from an object
- You need to convert data types back and forth multiple times
- You don't like your backend's API and
GraphQL
is not an option 😁
Installation
Install as dependency
npm install --save parakeet-mapper
# or
yarn add parakeet-mapper
Import and use
ES
import { mapTypes, mapFactory } from 'parakeet-mapper'
CommonJS
const { mapTypes, mapFactory } = require('parakeet-mapper');
Script tag
<!-- Modern es-modules: -->
<script>
import { mapTypes, mapFactory } from 'https://unpkg.com/parakeet-mapper';
</script>
<!-- Or a legacy IIFE script: -->
<script src="https://unpkg.com/parakeet-mapper/dist/parakeet-mapper.iife.js"></script>
<script>
// global variable parakeetMapper
const { mapTypes, mapFactory } = parakeetMapper;
</script>
API
parakeet-mapper
exposes several helpers to deal with type conversions.
All of them use the TypeMap interface to communicate.
name | overloads | description -----|-----------|----------------------------------- mapFactory | 3 | Accepts a TypeMap. Returns a function that accepts one input and returns an output converted using rules defined in the TypeMap. mapTypes | 3 | Same as mapFactory, but instead of returning a function, accepts input as its first argument and returns an output right away. Convertable | 2 | Class mixin. Allows creation of convertable classes.
TypeMap
object
It is a set of rules that define how the output type is made from the input type.\
The rules are simple:\
- Each key corresponds to a key in the output.
- Each value tells what to assign to that key from the input.
true
= the value is assigned from the same key in the input.- A string = the value is assigned from this string key in the input.
- An object = the value is assigned from this object's first key in the input and is processed using the value as converter.
- An array of a single element = the first element in the array is used as converter for the input value by the output key.
- A function = the value is mapped using this function from the input.
Examples
const input = {
transferred: 'foo',
renamed: 'bar',
converted: '42',
mapped: [1, 1, 2],
omitted: 'won\'t transfer to output'
};
const TypeMap = {
// Transferred straight to the output
transferred: true,
// Renamed from `renamed` into `outputRenamed`
outputRenamed: 'renamed',
// Renamed from `converted` into `convertedNumber`
// and converted from string to number
convertedNumber: { converted: Number },
// Mapped using a function and also renamed.
mappedSum: input => input.mapped.reduce((a, b) => a + b),
// Output object is also accessible in the function as a second parameter
// This allows to reuse operations for already converted values
// (like mappedSum and convertedNumber, in this example)
mappedPlusConverted: (_input, output) => output.mappedSum + output.convertedNumber
};
/* output */ {
transferred: 'foo',
outputRenamed: 'bar',
convertedNumber: 42, // Number('42')
mappedSum: 4,
mappedPlusConverted: 46 // 4 + 42
// notice the absence of `ommited` property
}
object/array shorthand with the same key
new in
v2.1.2
It's not necessary to specify the correct key in the conversion object:
const input = {
number: '42',
};
const TypeMap = {
// Simply converts from string to number using the `Number` function
number: { Number },
};
/* output */ {
number: 42, // Number('42')
// Notice that the property name stayed the same,
// even though we used a `{ Number: Number }` shorthand.
}
It only works if the input has the same property key as the output.
Can also be written using the array (tuple) syntax:
const input = {
number: '42',
};
const TypeMap = {
// Simply converts from string to number using the `Number` function
number: [Number],
};
/* output */ {
number: 42, // Number('42')
// Notice that the property name stayed the same,
// even though we used a `{ Number: Number }` shorthand.
}
mapFactory
function
A factory function that produces a converter from a TypeMap:
import { mapFactory } from 'parakeet-mapper';
const inputToOutput = mapFactory(TypeMap);
const output = inputToOutput(input); /* {
transferred: 'foo',
outputRenamed: 'bar',
convertedNumber: 42, // Number('42')
mappedSum: 4,
mappedPlusConverted: 46 // 4 + 42
// notice the absence of `ommited` property
} */
Overloads
This function has 3 overloads, all of which are needed for type safety and type inference (TypeScript).
There are basically 3 typed use-cases of using this function (hence 3 overloads):
- Input and output types are known, and TypeMap needs to convert them precisely.
declare const input: InputType; const inputConverter = mapFactory<InputType, OutputType>(TypeMap); // output is OutputType now
- Input type is known, output needs to be inferred from the TypeMap.
The first call without arguments is a noop. Reasons: first, second.declare const input: InputType; // The empty braces are here due to TS issues. const inputConverter = mapFactory<InputType>()(TypeMap);
- Input and output types are known, but the output type needs to be modified slightly.
The first call without arguments is a noop. Reasons: first, second.declare const input: InputType; // The empty braces are here due to TS issues. const inputConverter = mapFactory<InputType, OutputType>()(TypeMap);
mapTypes
function
Basically, a mapFactory, called in-place.
First Argument | Second Argument -----------------|------------------------------- The input object | Corresponding TypeMap
import { mapTypes } from 'parakeet-mapper';
const output = mapTypes(input, TypeMap); /* {
transferred: 'foo',
outputRenamed: 'bar',
convertedNumber: 42, // Number('42')
mappedSum: 4,
mappedPlusConverted: 46 // 4 + 42
// notice the absence of `ommited` property
} */
// Same as
// const output = mapFactory(TypeMap)(input);
Overloads
There are 3. All are semantically the same as the overloads of mapFactory, including the noop call:
/* 1 */ mapTypes<InputType, OutputType>(input, TypeMap);
/* 2 */ mapTypes<InputType>()(input, TypeMap);
/* 3 */ mapTypes<InputType, OutputType>()(input, TypeMap);
Wait
function
new inv2.1
A complementary function to mapFactory
that helps to flatten out promises
It produces a converter that returns a flat promise from a converter that returns an object with promises.
In a typical situation, when some convertations are asyncronous, you'd end up with this:
import { mapTypes } from 'parakeet-mapper';
// Imagine that this is requesting something from an API and returns a promise
const requestFromApi = (value) => Promise.resolve(value);
const input = {
a: ['a'],
b: 42,
c: 'c'
};
const getAandBfromAPI = mapFactory({
b: true,
a: { a: requestFromApi },
c: { c: requestFromApi }
});
const output = getAandBfromAPI(input);
// Result:
/* {
a: Promise<['a']>,
b: 42,
c: Promise<'b'>
} */
// Not very comfortable to await every single value after this
Now with wait
:
import { wait } from 'parakeet-mapper';
const waitForAandB = wait(getAandBfromAPI);
const output = getAandBfromAPI(input);
// Result:
/* Promise<{
a: ['a'],
b: 42,
c: 'b'
}> */
// Much more useful now
flattenPromises
function
new inv2.1
Internally used in wait
, flattens top-level promises in an object:
import { flattenPromises } from 'parakeet-mapper';
const objWithPromises = {
a: [Promise.resolve('a')],
b: 42,
c: Promise.resolve('b')
};
const flat = flattenPromises(objWithPromises);
// Result
/* Promise<{
a: ['a'],
b: 42,
c: 'b'
}> */
Convertable
function
new inv2.0
Allows to create classes from converters.
This makes possible adding extra functionality, including reverse convertations.
import { Convertable, mapFactory } from 'parakeet-mapper';
const inputConverter = () => mapFactory(mapTypes);
class Output extends Convertable(inputConverter) {}
const output = new Output(input);
console.log(output); /*
> Output {
transferred: 'foo',
outputRenamed: 'bar',
convertedNumber: 42,
mappedSum: 4,
mappedPlusConverted: 46
}
*/
Accepts a function that returns a converter as its only argument.
Returns a Convertable class with all the required functionality.
Can also infer arguments from its converter factory:
import { Convertable, mapFactory } from 'parakeet-mapper';
const input: InputType = {
foo: 'foo',
bar: 'bar'
}
const inputConverter = (convertFoo?: boolean) => mapFactory<InputType>()({
zoo: convertFoo,
zar: convertFoo ? 'bar' : 'foo'
});
class Output extends Convertable(inputConverter) {}
const outputWithFoo = new Output(input, /* convertFoo? */ true);
console.log(outputWithFoo); /*
> Output {
zoo: 'foo',
zar: 'bar'
}
*/
const outputWithoutFoo = new Output(input, /* convertFoo? */ false);
console.log(outputWithoutFoo); /*
> Output {
zar: 'foo'
}
*/
And accept a reverse converter:
import { Convertable, mapFactory } from 'parakeet-mapper';
const input: InputType = {
foo: 'foo',
bar: 'bar'
}
const inputConverter = (convertFoo?: boolean) => mapFactory<InputType>()({
zoo: convertFoo,
zar: convertFoo ? 'bar' : 'foo'
});
const outputConverter = (convertZoo?: boolean) => mapFactory<OutputType>()({
foo: convertZoo,
bar: convertZoo ? 'zar' : 'zoo'
});
class Output extends Convertable(inputConverter, outputConverter) {}
const output = new Output(input, /* convertFoo? */ true);
console.log(output); /*
> Output {
zoo: 'foo',
zar: 'bar'
}
*/
// Convert back to input type
const newInput = output.toInput(/* convertZoo? */ true);
console.log(newInput); /*
> {
foo: 'foo',
bar: 'bar'
}
*/
Convertable class
constructor
Accepts an input as its first argument and converter factory parameters as other spread arguments.
toInput
Available only if reverse converter was passed into the Convertable.
Accepts a spread of reverse converter arguments
Convertable.createConverter
static
A converter factory, passed to the Convertable.
Convertable.reverseConverter
static
A reverse converter factory, passed to the Convertable.