Javascript:将点分隔字符串转换为嵌套对象值

Javascript: Convert dot-delimited strings to nested object value

我有一堆对象属性以点分隔的字符串形式出现,例如 "availability_meta.supplier.price",我需要为 record['availability_meta']['supplier']['price'] 等分配相应的值。

并非所有东西都是 3 层深:许多只有 1 层深,很多都超过 3 层。

在 Javascript 中是否有一种以编程方式分配它的好方法?比如我需要:

["foo.bar.baz", 1]  // --> record.foo.bar.baz = 1
["qux.qaz", "abc"]  // --> record.qux.qaz = "abc"
["foshizzle", 200]  // --> record.foshizzle = 200

我想我可以一起破解一些东西,但我没有任何好的算法,所以我很感激你的建议。如果有帮助,我正在使用 lodash,并向其他可以快速完成此工作的库开放。

EDIT 这是在后端并且 运行 很少,所以没有必要优化大小、速度等。事实上代码可读性在这里会是一个加号对于未来的开发者。

编辑 2 这与引用的副本不同。也就是说,我需要能够为同一个对象多次执行此分配,并且 "duplicate" 答案每次都会简单地覆盖子键。请重新打开!

让您入门的东西:

function assignProperty(obj, path, value) {
    var props = path.split(".")
        , i = 0
        , prop;

    for(; i < props.length - 1; i++) {
        prop = props[i];
        obj = obj[prop];
    }

    obj[props[i]] = value;

}

假设:

var arr = ["foo.bar.baz", 1];

您可以这样称呼它:

assignProperty(record, arr[0], arr[1]);

示例:http://jsfiddle.net/x49g5w8L/

随心所欲

record['foo.bar.baz'] = 99;

但这将如何工作?它严格适用于 V8 环境(Chrome 或 Node 和谐)的冒险者,使用 Object.observe。我们观察对象并捕获新属性的添加。当 "property" foo.bar.baz 添加(通过赋值)时,我们检测到这是一个带点的 属性,并将其转换为对 record['foo']['bar.baz'] 的赋值(创建 record['foo'] 如果它不存在),它又被转换为对 record['foo']['bar']['baz'] 的赋值。它是这样的:

function enable_dot_assignments(changes) {

    // Iterate over changes
    changes.forEach(function(change) {

        // Deconstruct change record.
        var object = change.object;
        var type   = change.type;
        var name   = change.name;

        // Handle only 'add' type changes
        if (type !== 'add') return;

        // Break the property into segments, and get first one.
        var segments = name.split('.');
        var first_segment = segments.shift();

        // Skip non-dotted property.
        if (!segments.length) return;

        // If the property doesn't exist, create it as object.
        if (!(first_segment in object)) object[first_segment] = {};

        var subobject = object[first_segment];

        // Ensure subobject also enables dot assignments.
        Object.observe(subobject, enable_dot_assignments);

        // Set value on subobject using remainder of dot path.
        subobject[segments.join('.')] = object[name];

        // Make subobject assignments synchronous.
        Object.deliverChangeRecords(enable_dot_assignments);

        // We don't need the 'a.b' property on the object.
        delete object[name];
    });
}

现在你可以做

Object.observe(record, enable_dot_assignments);
record['foo.bar.baz'] = 99;

但是请注意,此类分配将是异步的,这可能适合您也可能不适合您。要解决这个问题,请在赋值后立即调用 Object.deliverChangeRecords。或者,虽然在语法上不那么令人愉快,但您可以编写一个辅助函数,同时设置观察者:

function dot_assignment(object, path, value) {
    Object.observe(object, enable_dot_assignments);
    object[path] = value;
    Object.deliverChangeRecords(enable_dot_assignments);
}

dot_assignment(record, 'foo.bar.baz', 99);

也许像这个例子。如果没有提供对象,它将扩展提供的对象或创建一个对象。它本质上是破坏性的,如果你提供对象中已经存在的键,但如果这不是你想要的,你可以改变它。使用 ECMA5。

/*global console */
/*members split, pop, reduce, trim, forEach, log, stringify */
(function () {
    'use strict';

    function isObject(arg) {
        return arg && typeof arg === 'object';
    }

    function convertExtend(arr, obj) {
        if (!isObject(obj)) {
            obj = {};
        }

        var str = arr[0],
            last = obj,
            props,
            valProp;

        if (typeof str === 'string') {
            props = str.split('.');
            valProp = props.pop();
            props.reduce(function (nest, prop) {
                prop = prop.trim();
                last = nest[prop];
                if (!isObject(last)) {
                    nest[prop] = last = {};
                }

                return last;
            }, obj);

            last[valProp] = arr[1];
        }

        return obj;
    }

    var x = ['fum'],
        y = [
            ['foo.bar.baz', 1],
            ['foo.bar.fum', new Date()],
            ['qux.qaz', 'abc'],
            ['foshizzle', 200]
        ],
        z = ['qux.qux', null],
        record = convertExtend(x);

    y.forEach(function (yi) {
        convertExtend(yi, record);
    });

    convertExtend(z, record);
    document.body.textContent = JSON.stringify(record, function (key, value, Undefined) {
        /*jslint unparam:true */
        /*jshint unused:false */
        if (value === Undefined) {
            value = String(value);
        }

        return value;
    });
}());

你在问题中提到了 lodash,所以我想我应该添加它们的简单对象 set()get() 函数。只需执行以下操作:

_.set(record, 'availability_meta.supplier.price', 99);

您可以在此处阅读更多相关信息:https://lodash.com/docs#set

这些函数还可以让你做更复杂的事情,比如指定数组索引等:)

这个呢?

function convertDotPathToNestedObject(path, value) {
  const [last, ...paths] = path.split('.').reverse();
  return paths.reduce((acc, el) => ({ [el]: acc }), { [last]: value });
}

convertDotPathToNestedObject('foo.bar.x', 'FooBar')
// { foo: { bar: { x: 'FooBar' } } }