auto-object-mapper

0.2.3 • Public • Published

auto-object-mapper

这是一个源对象目标对象的映射工具,支持通过不同指令组合方式,实现不同的映射效果

关于

为什么要编写这个代码库,它能解决什么问题?为了从那些无聊枯燥毫无营养,但是又必须去做的重复工作中解脱出来,编写一个ObjectObject的对象属性复制映射代码库是值得的。我希望这个代码库在功能上能够支持,通过绑定不同指令的方式去干涉并改变默认的对象映射行为。通过不同的指令组合在一起使用,能够达到在指令层面上的逻辑编程的目的。到目前为止,我并没有发现能够完全满足我需要的已经存在的其他代码库,所以我决定自己编写一个,以解决遇到的困境

安装

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]

Package Sidebar

Install

npm i auto-object-mapper

Weekly Downloads

1

Version

0.2.3

License

ISC

Unpacked Size

28.3 kB

Total Files

12

Last publish

Collaborators

  • lr5420511