Converts JavaScript objects from one format to another.
Supports asymmetric mapping.
Simple, powerful, composable, strongly-typed.
Install via npm install two-way-mapper
Import via import { mapper } from 'two-way-mapper'
All methods on the mapper
namespace return a Mapper
, which is an interface as follows:
interface Mapper<TLeft, TRight> {
map(input: TLeft): TRight;
reverse(input: TRight): TLeft;
}
As you can see, the map
method converts from TLeft
to TRight
. The reverse
method converts from TRight
to TLeft
.
Let's convert between two similar User
types:
const userA: UserA = { name: "Scott", active: "yes" };
const userB: UserB = { name: "Scott", active: true };
With mapper.properties
, we supply a Mapper
for each property:
import { mapper } from "two-way-mapper";
const userMapper = mapper.properties<UserA, UserB>({
// Properties with the same name & type can simply be copied:
name: mapper.copy,
// Properties with different types can be mapped:
active: mapper.convert(
(active) => active === "yes",
(active) => (active ? "yes" : "no")
)
});
And now userMapper
converts data from UserA
to UserB
:
// The `map` function takes UserA and returns UserB:
const userB = userMapper.map(userA);
// Likewise, the `reverse` function takes UserB and returns UserA:
const userA = userMapper.reverse(userB);
Let's convert between two different User
types:
const userA: UserA = { fullName: "Scott Rippey", status: 'active' };
const userB: UserB = { name: "Scott Rippey", active: boolean };
With mapper.twoWay
, we use two mapping objects, for mapping properties in both directions:
const userMapper = mapper.twoWay<UserA, UserB>(
{
name: (input) => input.fullName,
active: (input) => input.status === 'active',
},
{
fullName: (input) => input.name,
status: (input) => input.active ? 'active' : 'inactive',
}
)
And now, use userMapper.map
and .reverse
to map between UserA
to UserB
.
Sometimes, we only want to convert data in one direction.
A one-way mapper only converts from A to B, but not from B to A.
const userA: UserA = { friends: [ { ... }, { ... }, { ... } ] };
const userB: UserB = { friendsCount: 3 };
const userMapper = mapper.oneWay<UserA, UserB>({
friendsCount: (input) => input.friends.length
});
The mapper still implements the two-way Mapper
interface, but the reverse
method does nothing.
oneWayReverse<A, B>()
works the same as oneWay
, but it only implements the reverse
method that converts from B
to A
(and the map
method does nothing).
Now, let's convert between two User
types that have a mix of similar and different properties.
const userA: UserA = { id: 5, name: "Scott", status: "active" };
const userB: UserB = { id: 5, name: "Scott", active: true };
We can use mapper.combine
to use both mapper.properties
and mapper.twoWay
in the same mapper:
const userMapper = mapper.combine(
mapper.properties<UserA, UserB>({
id: mapper.copy,
name: mapper.copy,
}),
mapper.twoWay<UserA, UserB>({
active: (input) => input.status === "active",
}, {
status: (input) => input.active ? "active" : "inactive",
})
);
The mapper
namespace also contains several utilities to make type-safe mapping easier.
You can also use these strongly-typed variants:
mapper.copyAs<T>()
mapper.cast<TLeft, TRight>()
const userMapper = mapper.properties<UserA, UserB>({
// Converts all items in the array using itemMapper:
items: mapper.array(itemMapper),
// Copy the value as-is:
id: mapper.copy,
// Same as copy, but ensures both types are strings:
name: mapper.copyAs<string>(),
// Cast between two types (value is still copied as-is):
status: mapper.cast<StatusEnumA, StatusEnumB>(),
// Convert types manually:
active: mapper.convert(
input => input.active ? 'yes' : 'no',
input => input.active === 'yes'
),
// Use a constant value when mapping:
typename: mapper.constant('UserA', 'UserB'),
});