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

12
我有一堆以点分隔的字符串作为对象属性,例如"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,并且也愿意尝试其他可能能快速完成此任务的库。

编辑

这是在后端运行的,而且不频繁,所以不需要为大小、速度等进行优化。实际上,代码可读性对于未来的开发人员来说会是一个加分项。

编辑2

进一步澄清,我需要能够多次为同一个对象执行此任务。


5
  1. . 进行分割。
  2. 使用 for 循环。
  3. ???????
  4. 利润!111
- zerkms
1
@tyler,实际上什么都没有 - 你分割、迭代和赋值。 - zerkms
@zerkms,你能写一个快速示例吗?我觉得有多个长度的赋值部分很棘手。 - Tyler
@loganfsmyth 这对于读取属性来说没问题,但 OP 想要写入它们。 - Phil
@torazaburo 这不是重复的。请参见我上面的 EDIT 2。 - Tyler
显示剩余12条评论
6个回答

14

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

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

你可以在这里阅读更多相关内容:https://lodash.com/docs#set

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


1
同意。虽然了解如何实现这样的东西可以成为数据结构方面的良好练习,但不必自己实现和维护它几乎总是一个优点。由于OP明确提到已经在使用lodash,因此这似乎应该是被接受的答案。 - codermonkeyfuel

8

一些帮助你入门的内容:

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]);

Example: http://jsfiddle.net/x49g5w8L/


注意,当您调用assignProperty时缺少一个参数。 - ianaya89
@PastorBones:我已经撤销了你的编辑。它的写法是按设计来的。这很微妙,但之所以到 length - 1 是因为在循环结束后,i 等于最后一个属性(在示例中是 baz)。因此,函数中的最后一行将值分配给正确的属性。 - Andrew Whitaker
@AndrewWhitaker,我几分钟后就发现了我的错误,你比我更快地发现了它,谢谢你保持谦虚...现在我要写9001行代码来纠正我的错误。 - Pastor Bones
除非对象已经具有设置属性之前的所有属性,否则这不起作用。如果foo没有在上面定义bar对象,您将收到未定义的错误。这基本上使它无用。 - Zambonilli
@Zambonilli:你说得完全正确,对于那种用例来说它是无用的,因为它不是为此而设计的。它是为了回答OP的问题而设计的,这意味着(至少对我来说)record及其子元素具有必要的属性才能使其正常工作。 - Andrew Whitaker
很好。对于那些寻找填充缺失属性的解决方案的人来说,第二个答案lodash.set可以处理子属性。 - Zambonilli

6

这个怎么样?

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' } } }

我正在使用GraphQL,这与我需要深度嵌套更新的内容非常接近。参见:https://medium.com/pro-react/a-brief-talk-about-immutability-and-react-s-helpers-70919ab8ae7c - Jeff Lowery
谢谢。我真的只是在浪费时间尝试自己编写这个。 - Patrick Michaelsen

2

只需要做

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

但是这会怎样工作呢?它仅适用于具有V8环境(Chrome或Node Harmony)的冒险家,使用Object.observe。我们观察对象并捕获新属性的添加。当通过赋值添加“属性”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);

1
也许类似于这个例子。它将扩展一个提供的对象,或者如果没有提供对象,则创建一个新对象。它具有破坏性质,如果您提供的键已经存在于对象中,则会覆盖原有值,但如果这不是您想要的结果,您可以更改它。使用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;
    });
}());


0

这是一个老问题,但如果还有人在寻找解决方案,可以尝试这个。

function restructureObject(object){
    let result = {};
    for(let key in object){
        const splittedKeys = key.split('.');
        if(splittedKeys.length === 1){
            result[key] = object[key];
        }
        else if(splittedKeys.length > 2){
            result = {...result, ...{[splittedKeys.splice(0,1)]: {}} ,...restructureObject({[splittedKeys.join('.')]: object[key]})}
        }else{
            result[splittedKeys[0]] = {[splittedKeys[1]]: object[key]}
        }
        
    }

    return result
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接