auto-object-mapper
v0.2.3
Published
这是一个源对象到目标对象的映射工具,支持通过不同指令的组合方式,实现不同的映射效果。
Downloads
2
Readme
auto-object-mapper
这是一个源对象
到目标对象
的映射工具,支持通过不同指令
的组合
方式,实现不同的映射效果
。
关于
为什么要编写这个代码库,它能解决什么问题?为了从那些无聊枯燥
且毫无营养
,但是又必须去做的重复工作
中解脱出来,编写一个Object
到Object
的对象属性复制映射代码库是值得的。我希望这个代码库在功能上能够支持,通过绑定不同指令
的方式去干涉并改变
默认的对象映射行为。通过不同的指令组合
在一起使用,能够达到在指令层面上的逻辑编程
的目的。到目前为止,我并没有发现能够完全满足我需要的已经存在的其他代码库,所以我决定自己编写一个,以解决遇到的困境
。
安装
npm install --save auto-object-mapper
用法
通过配置对象字面量
并初始化
映射对象的方式,告诉程序映射的源头
在哪里,映射到何处
。键值对中的key表示源对象
中映射源头的查找路径
,它所对应的value则表示目标对象
中映射目的地的查找路径
。
初始化对象映射实例
因为通过该包导出的是一个ObjectMapper类型,我们传递一个映射清单
给ObjectMapper类型的构造函数
,可以直接初始化出一个对象映射实例
。就像这样:
const ObjectMapper = require('auto-object-mapper');
const mapper = new ObjectMapper({ [srcPath]: [tarPath] });
// srcPath和tarPath是源对象查找路径和目标对象查找路径
源对象查找路径
源对象
的映射源头查找路径
可以是一个简单
的字符串
,代表单一的属性
,就像这样:
{
'baz': 'bas',
'ase': 'taz.bas'
}
源对象
的映射源头查找路径
也可以是一个通过特殊字符 . [ ] 串联起来的路径字符串
,代表一个可以找到对象指定属性的查找路径
,就像这样:
{
'baz.bas': 'foo',
'baz[0].bas': 'xzz'
}
源对象
的映射源头查找路径
也可以是一个空字符串
,代表源对象本身
,就像这样:
{
'': 'baz'
// 空字符串表示源对象自身
}
源对象
的映射源头查找路径
还可以是一个在源对象
中不存在
的路径
,这时候通过查找路径
查找到的值为 undefined,就像这样:
const source = { baz: {} };
{
'baz.bas.foo': 'das'
// 因为查找路径baz.bas.foo在源对象中不存在,故查找到的值是undefined
}
目标对象查找路径
目标对象
的映射目的地查找路径
可以是一个简单的字符串
,代表一个单一的对象属性
或者是代表一个对象指定属性的查找路径
。就像这样:
{
'baz': 'bas'
// 可以是最简单的对象属性
}
{
'baz': 'bas.foo[2].daz'
// 也可以是多个对象属性串联后的查找路径
}
目标对象
的映射目的地查找路径
也可以是一个对象
,它不仅可以代表一个单一的对象属性
或者是代表一个对象指定属性的查找路径
,甚至还可以在其中配置绑定指令
需要的选项
。就像这样:
{
'baz': { key: 'foo' },
'bas': { key: 'daz.das' }
}
{
'baz': {
key: 'foo.das?+!',
default: 1,
expand: () => [true]
// 可以在其中配置绑定指令需要的选项
}
}
目标对象
的映射目的地查找路径
还可以是一个数组
,代表一个源对象
的映射源头查找路径
查找到的值
,可以同时映射
到目标对象
的多个查找路径
查找到的目的地
上。就像这样:
{
'baz': [
'bas.foo',
{ key: 'das.daz' },
{
key: 'daf.foo?->!',
default: () => 2,
transform: (sval, tval, prop, host, tar) =>
[sval, tval, '[status]', tar],
increment: /^exclude/
}
]
// 源对象的映射源头查找路径查找的值,可以映射到多个目的地上
}
目标对象
的映射目的地查找路径
在目标对象
中所查找到的所有节点属性
,必须保证除了最后一个节点属性
之外的其他节点属性
,在目标对象
中查找到的值是Object类型的对象且不能
为null,否则将跳过
当前的单次映射。就像这样:
const mapper = new ObjectMapper({ 'a.b': ['b.c.d', 'b.e.f'] });
const result = mapper.appear({ 'a': { 'b': 5 } }, { 'b': { 'c': 9 } });
// result是{ b: { c: 9, e: { f: 5 } } }
目标对象
的映射目的地查找路径
中,可以通过配置ignore选项来选择,当在源对象
中查找路径不存在
时,是否跳过
当前的单次映射。就像这样:
const mapper = new ObjectMapper({
'a.b': 'c',
'a.b.c.d': [{ key: 'd.e.g', ignore: true }, 'e']
});
const result = mapper.appear({ a: { b: 7 } });
// result是{ c: 7, e: undefined }
对象映射
由于ObjectMapper类型提供了一个 .appear的原型方法
,该类型的对象可以直接通过调用 .appear的方式进行源对象
到目标对象
的映射。值得一提的是,对象映射的状态接收方目标对象
,它既可以是一个初始无状态
的对象,同时也可以是一个初始带有状态
的对象。
.appear(source[, destination])
- 参数
source
,必需,代表参与映射的源对象
。 - 参数
destination
,可选,代表参与映射的目标对象
,当不提供这个参数时,将默认目标对象
是一个初始无状态
的对象。 - 返回值是
destination
本身。
const destination = { a: { b: 10 } };
const result1 = mapper.appear({ c: 5 });
// result1实际上就是默认在内部创建的一个初始无状态的对象
const result2 = mapper.appear({ c: 5 }, destination);
// result2实际上就是destination本身
映射替换策略
默认的映射替换策略
是保守的
,它不具有任何攻击性
、破坏性
和侵略性
,不会去改变目标对象
已经存在
的某个状态。它只在保持目标对象
原有状态的基础上,进行状态的增量修改
。将这种策略作为默认的映射替换策略
,这不一定是一个坏主意。
const mapper = new ObjectMapper({ 'a.b': 'c.d' });
const result1 = mapper.appear({ a: { b: 2 } });
// result1是{ c: { d: 2 } },目标查找路径在目标对象中不存在,根据默认的替换策略,可以进行增量修改
const result2 = mapper.appear({ a: { b: 2 } }, { c: { e: 4 } });
// result2是{ c: { e: 4, d: 2 } },目标查找路径在目标对象中不存在,根据默认的替换策略,可以进行增量修改
const result3 = mapper.appear({ a: { b: 2 } }, { c: { d: 3 } });
// result3是{ c: { d: 3 } },目标查找路径在目标对象中存在,根据默认的替换策略,需要跳过替换且保持目标对象原来的状态不变
绑定指令
实际工作中我们发现,在对象映射
过程中通过某种手段去干涉并改变
默认的对象映射行为
是非常有用的。在最终改变目标对象状态
之前,就尝试去做这些事情,为什么不可以呢?有两种方案放在我们面前,一种是通过transform回调函数的方式,直接在最终改变目标对象状态
之前,就去调用它。另一种则是通过组合
各种基础指令
的方式,直接在最终改变目标对象状态
之前,去执行这些指令。很明显第二种方案更符合我们的要求,它允许我们通过不同基础指令
的组合
方式,用最少的代码去改变对象映射
过程。默认提供的基础指令
有以下几种:
基础指令 ?
这个基础指令
主要去改变查找路径
在源对象
中查找到的值
。如果当前指令接收到的源对象值
是null或者是undefined,则指令会试图去用目标对象查找路径
中配置的default项,替代原来的源对象值
,否则跳过
本次指令处理过程。
const mapper = new ObjectMapper({
'a': { key: 'b?', default: 4 },
'c': [
{ key: 'd?', default: 5 },
{
key: 'e?',
default: (tval, src, tar) => 6
}
],
'f.g': { key: 'h?', default: 7 }
});
const result = mapper.appear({ a: 1, c: null });
// result是{ b: 1, d: 5, e: 6, h: 7 }
基础指令 >
这个基础指令
主要去改变查找路径
在源对象
中查找到的值
。如果当前指令接收到的源对象值
是Object类型的对象、null或者是undefined,并且接收到的目标对象值
是Object类型的对象同时不为null,则指令会使用源对象值
在目标对象值
的基础上,进行状态的增量合并
,否则跳过
本次指令处理过程。还可以通过在目标对象查找路径
中配置increment项来排除一些源对象值
中的属性,被排除的属性将不参与本次合并。
const mapper = new ObjectMapper({
'a': 'b>',
'c': [
{ key: 'd>!' },
{ key: 'e>!', increment: /^nam/ }
]
});
const result = mapper.appear({ a: 2, c: { f: 3, g: 4, name: 'liao' } }, { d: { f: 5, h: 6 }, e: { f: 5, h: 6 } });
// result是{ d: { f: 5, h: 6, g: 4, name: 'liao' }, e: { f: 5, h: 6, g: 4 }, b: 2 }
基础指令 *
这个基础指令
主要去改变查找路径
在源对象
中查找到的值
。如果当前指令接收到的源对象值
是Object类型的对象、null或者是undefined,并且接收到的目标对象值
是Object类型的对象同时不为null,则指令会使用源对象值
在目标对象值
的基础上,进行状态的覆盖合并
,否则跳过
本次指令处理过程。还可以通过在目标对象查找路径
中配置merge项来排除一些源对象值
中的属性,被排除的属性将不参与本次合并。
const mapper = new ObjectMapper({
'a': 'b*',
'c': [
{ key: 'd*!' },
{ key: 'e*!', merge: /^nam/ }
]
});
const result = mapper.appear({ a: 2, c: { f: 3, g: 4, name: 'liao' } }, { d: { f: 5, h: 6 }, e: { f: 5, h: 6 } });
// result是{ d: { f: 3, h: 6, g: 4, name: 'liao' }, e: { f: 3, h: 6, g: 4 }, b: 2 }
基础指令 +
这个基础指令
主要去改变查找路径
在源对象
中查找到的值
。如果当前指令接收到的目标对象值
是Array类型的对象,则指令会使用源对象值
在目标对象值
的基础上,在一定的连接规则
下进行数组的连接,否则跳过
本次指令处理过程。默认的数组连接规则
规定,源对象值
是在目标对象值
的尾部
,与其连接为一个新的数组。若你想改变
默认的数组连接规则
,可以在目标对象查找路径
中配置expand项来改变连接规则
。
const mapper = new ObjectMapper({
'a': ['b+', 'c+!'],
'd': [
'e+!',
{
key: 'f+!',
expand: (sval, tval, src, tar) => [true, 1]
// 返回值[源对象值是否单独参与连接, 在目标对象值何处进行连接]
}
]
});
const result = mapper.appear({ a: 2, d: [3, 4] }, { c: [1, 1], e: [2, 2], f: [5, 5] });
// result是{ c: [ 1, 1, 2 ], e: [ 2, 2, 3, 4 ], f: [ 5, [ 3, 4 ], 5 ], b: 2 }
基础指令 !
这个基础指令
主要去改变查找路径
在目标对象
中查找到的值
。如果当前指令接收到的目标对象值
所在的宿主对象
是一个Object类型的对象且不为null,则指令会使用源对象值
去立即更新目标对象
中的相应状态
,否则跳过
本次对目标对象
的状态更新
。该指令是对默认映射替换策略
的一种互补,具有很强的攻击性
、破坏性
和侵略性
,在使用之前应该清楚的知道正在做什么?
const mapper = new ObjectMapper({
'a': 'b',
'c': ['d!', '!']
});
const result = mapper.appear({ a: 1, c: 4 }, { b: 2, d: 3 });
// result是{ b: 2, d: 4 }
基础指令 -
这个基础指令
主要去改变查找路径
在源对象
中查找到的值
、在目标对象
中查找到的值
、在目标对象
中查找到的宿主对象
和在宿主对象
中目标对象值
所对应的属性
。可以通过在目标对象查找路径
中配置transform项,来完全改变默认的对象映射行为
。
const mapper = new ObjectMapper({
'a': [
'b-',
{
key: '-+',
transform: (sval, tval, prop, host, tar) =>
[Array(sval).fill(3), [1], '[status]', tar]
}
]
});
const result = mapper.appear({ a: 2 });
// result是{ b: 2, '[status]': [1, 3, 3] }
基础指令 #
这个基础指令
主要去改变查找路径
在源对象
中查找到的值
。如果当前指令接收到的源对象值
是Array类型的对象,则指令会在源对象值
的基础上进行数组查找
,否则跳过
本次指令处理过程。可以通过在目标对象查找路径
中配置search项来指定数组查找
的方式。
const mapper = new ObjectMapper({
'a': { key: 'b#', search: 'baz.bas' },
'b': [
'c#',
{
key: 'd#', search: (sval, src) => 'baz.bas[1]'
}
]
});
const result = mapper.appear({
a: 2,
b: [
{ baz: { bas: [[0, 4]] } },
{ baz: 0 },
{ baz: { bas: { '1': 9 } } },
{ baz: { bas: [[5, 6]] } }
]
});
// result是{ b: 2, c: [{ baz: { bas: [[0, 4]] } }, { baz: 0 }, { baz: { bas: { '1': 9 } } }, { baz: { bas: [[5, 6]] } }], d: [ 4, 9, 6 ] }
组合基础指令 ?->*+#!
通过组合搭配基础指令
的方式,去干涉并改变
默认的对象映射行为
是让人愉悦的。
const mapper = new ObjectMapper({
'a': [
'b',
{
key: 'c.d?-+!',
default: tval => Object.keys(tval).length ? 2 : 3,
transform: (sval, tval, prop, host) => [
Array(sval).fill(sval),
Array.isArray(tval) ? tval : [],
prop,
host
],
expand: (_, tval) => tval.length ?
[true, Math.floor(tval.length / 2)] : [false]
}
]
});
const destination = {};
mapper.appear({}, destination);
// destination是{ b: undefined, c: { d: [ 3, 3, 3 ] } }
mapper.appear({ a: null }, destination);
// destination是{ b: undefined, c: { d: [ 3, [2, 2], 3, 3 ] } }
未来的拓展
可以通过在ObjectMapper上调用install函数,安装
其他的自定义指令
插件。
ObjectMapper.install(plugin)
- 参数
plugin
,必需
,表示插件本身,它会接受ObjectMapper
作为参数。 - 返回值,
ObjectMapper
本身。
function XXX_mapper(rt) {
rt.presets.directives['X'] = handler;
};
ObjectMapper.install(XXX_mapper);
handler(sval, tval, prop, host, fields, src, tar, index, ops)
- 参数
sval
,表示当前指令接收
到的源对象值
。 - 参数
tval
,表示当前指令接收
到的目标对象值
。 - 参数
prop
,表示当前指令接收
到的目标对象值
,在宿主对象
中对应的属性
。 - 参数
host
,表示当前指令接收
到的目标对象值
所在的宿主对象
。 - 参数
fields
,表示目标对象查找路径
中的单个配置选项
。 - 参数
src
,表示参与映射的源对象
。 - 参数
tar
,表示参与映射的目标对象
。 - 参数
index
,表示当前执行的指令
在指令集合
中的索引
。 - 参数
ops
,表示当前需要执行的整个指令集合
。 - 返回值,必须返回一个
数组
[sval, tval, prop, host]。