【问题标题】:Javascript: Convert dot-delimited strings to nested object valueJavascript:将点分隔的字符串转换为嵌套对象值
【发布时间】:2015-03-19 11:00:55
【问题描述】:

我有一堆像"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这是在后端并且不经常运行,因此没有必要针对大小、速度等进行优化。事实上,代码可读性将是未来开发人员的一个加分项。

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

【问题讨论】:

  • 1.将其拆分为. 2. 使用for。 3. ?????? 4. 利润!!!111
  • 哈,是的,这就是??????? 部分
  • Angular 的 $parse 在这方面做得很好。也许看看source code
  • @tyler 实际上什么都没有 - 你拆分、迭代和分配
  • @zerkms 你能写一个简单的例子吗?我觉得有多个长度的赋值部分很棘手

标签: javascript arrays object lodash


【解决方案1】:

让你开始的东西:

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/

【讨论】:

  • 请注意,当您调用 assignProperty 时,您缺少一个参数。
  • @PastorBones:我已经回滚了你的编辑。它的编写方式是按照设计的。这很微妙,但它转到length - 1 的原因是因为循环结束后,i 等于最后一个属性(在示例中为baz)。所以函数中的最后一行将值分配给正确的属性。
  • @AndrewWhitaker 我实际上在几分钟后发现了我的错误,你打败了我,谢谢你保持谦虚......我现在将为我的错误编写 9,001 行代码。
  • 这不起作用,除非您的对象已经具有导致设置属性的所有属性。如果 foo 没有上面已经定义的 bar 对象,你会得到一个未定义的错误。这几乎使它毫无用处。
  • @Zambonilli:你说的完全正确,它对那个用例没用,因为它不是为它设计的。它是为 OP 的问题而设计的,这意味着(至少对我而言)record 及其子级具有必要的属性才能使其正常工作。
【解决方案2】:

做吧

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

但是这将如何工作?使用Object.observe,它只适合 V8 环境(Chrome 或 Node 和谐)的冒险者。我们观察对象并捕获新属性的添加。当添加“属性”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);

【讨论】:

    【解决方案3】:

    也许像这个例子。如果没有提供对象,它将扩展提供的对象或创建一个对象。如果您提供对象中已经存在的密钥,它本质上是破坏性的,但如果这不是您想要的,您可以更改它。使用 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;
        });
    }());

    【讨论】:

      【解决方案4】:

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

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

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

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

      【讨论】:

      • 同意。虽然了解如何实现这样的东西可能是数据结构的一个很好的练习,但不必自己实现和维护它几乎总是一个加分项。由于 OP 明确提到已经使用 lodash,这似乎应该是公认的答案。
      【解决方案5】:

      这个怎么样?

      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,这接近于我需要使用深度嵌套更新的那种东西。见:medium.com/pro-react/…
      猜你喜欢
      • 1970-01-01
      • 2021-02-21
      • 2011-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-06
      • 2021-12-06
      相关资源
      最近更新 更多