Javascript: Convert dot-delimited strings to nested object value

不想你离开。 提交于 2020-01-10 19:36:14

问题


I have a bunch of object attributes coming in as dot-delimited strings like "availability_meta.supplier.price", and I need to assign a corresponding value to record['availability_meta']['supplier']['price'] and so on.

Not everything is 3 levels deep: many are only 1 level deep and many are deeper than 3 levels.

Is there a good way to assign this programmatically in Javascript? For example, I need:

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

I imagine I could hack something together, but I don't have any good algorithm in mind so would appreciate suggestions. I'm using lodash if that's helpful, and open to other libraries that may make quick work of this.

EDIT this is on the backend and run infrequently, so not necessary to optimize for size, speed, etc. In fact code readability would be a plus here for future devs.

EDIT 2 This is NOT the same as the referenced duplicate. Namely, I need to be able to do this assignment multiple times for the same object, and the "duplicate" answer will simply overwrite sub-keys each time. Please reopen!


回答1:


You mentioned lodash in your question, so I thought I should add their easy object set() and get() functions. Just do something like:

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

You can read more about it here: https://lodash.com/docs#set

These functions let you do more complex things too, like specify array indexes, etc :)




回答2:


Something to get you started:

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;

}

Assuming:

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

You'd call it using:

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

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




回答3:


What about this?

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



回答4:


Just do

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

But how would this work? It's strictly for the adventurous with a V8 environment (Chrome or Node harmony), using Object.observe. We observe the the object and capture the addition of new properties. When the "property" foo.bar.baz is added (via an assignment), we detect that this is a dotted property, and transform it into an assignment to record['foo']['bar.baz'] (creating record['foo'] if it does not exist), which in turn is transformed into an assignment to record['foo']['bar']['baz']. It goes like this:

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

Now you can just do

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

Beware, however, that such assignments will be asynchronous, which may or may not work for you. To solve this, call Object.deliverChangeRecords immediately after the assignment. Or, although not as syntactically pleasing, you could write a helper function, also setting up the observer:

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



回答5:


Something like this example perhaps. It will extend a supplied object or create one if it no object is supplied. It is destructive in nature, if you supply keys that already exist in the object, but you can change that if that is not what you want. Uses 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;
    });
}());


来源:https://stackoverflow.com/questions/28058519/javascript-convert-dot-delimited-strings-to-nested-object-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!