Home Reference Source

src/adapter-util.js

import dotProp from 'dot-prop';
import defaultsDeep from './fav-prop.defaults-deep.js';

/**
 * 获取 path 分组
 * 
 * @param {string} path 
 * @return {Array<string>}
 * @see https://github.com/sindresorhus/dot-prop
 */
function getPathSegments(path) {
    const pathArray = path.split('.');
    const parts = [];

    for (let i = 0; i < pathArray.length; i++) {
        let p = pathArray[i];

        while (p[p.length - 1] === '\\' && pathArray[i + 1] !== undefined) {
            p = p.slice(0, -1) + '.';
            p += pathArray[++i];
        }

        parts.push(p);
    }

    return parts;
}

/**
 * 根据 path 获取父级对象
 * 
 * 例如 path 为: a.b.c
 * 我们需要先拿到 a.b 这个目标对象
 * 
 * @param {object} target 
 * @param {string} propertyPath
 * @return {object}
 */
function getParentObject(target, propertyPath) {
    var parentObject = null;

    var pathArray = getPathSegments(propertyPath);
    if (pathArray.length === 1) {
        parentObject = target;
    } else {
        var parentPropertyPath = pathArray.slice(0, pathArray.length - 1).join('.');
        parentObject = dotProp.get(target, parentPropertyPath);
        if (typeof parentObject === 'undefined' || parentObject === null) {
            parentObject = {};
            dotProp.set(target, parentPropertyPath, parentObject);
        }
    }

    return parentObject;
}

/**
 * 适配数据
 * 
 * @param {object} target 
 * @param {object} propertyAdapter
 * @param {object} register
 * @return {object}
 */
export function adapt(target, propertyAdapter, register) {
    for (var propertyPath in propertyAdapter) {
        var adapter = propertyAdapter[propertyPath] || {};

        // 通过 getter/setter 实现转换器转换数据
        if (adapter.transformer) {
            var pathArray = getPathSegments(propertyPath);
            var propertyName = pathArray[pathArray.length - 1];
            var definePropertyTarget = getParentObject(target, propertyPath);

            var propertyValue = dotProp.get(target, propertyPath);
            dotProp.set(register, propertyPath, propertyValue);

            // 将原属性改写为 getter/setter, 属性值保存到寄存器上
            Object.defineProperty(definePropertyTarget, propertyName, {
                get: function() {
                    // TODO 如果要适配的属性本身是一个 getter/setter 呢?
                    // 从寄存器上获取属性值
                    var original = dotProp.get(register, propertyPath);
                    return adapter.transformer(original, target);
                },
                set: function(value) {
                    // 将属性值保存到寄存器上
                    dotProp.set(register, propertyPath, value);
                },
                configurable: true,
                enumerable: true
            });
        }
    }

    return target;
}

/**
 * 还原数据
 * 
 * @param {object} target 
 * @param {object} propertyAdapter 
 * @param {object} register 
 * @return {object}
 */
export function restore(target, propertyAdapter, register) {
    var source = defaultsDeep({}, target);

    for (var propertyPath in propertyAdapter) {
        var adapter = propertyAdapter[propertyPath] || {};

        // 去除 getter/setter 转换器, 还原为原始值
        if (adapter.transformer) {
            var pathArray = getPathSegments(propertyPath);
            var propertyName = pathArray[pathArray.length - 1];
            // 原始值在寄存器上
            var propertyValue = dotProp.get(register, propertyPath);
            var definePropertyTarget = getParentObject(source, propertyPath);

            Object.defineProperty(definePropertyTarget, propertyName, {
                value: propertyValue,
                configurable: true,
                enumerable: true,
                writable: true
            });
        }
    }

    return source;
}