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]);
随心所欲
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' } } }
我有一堆对象属性以点分隔的字符串形式出现,例如 "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]);
随心所欲
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' } } }