背景
在实际业务场景中,我们往往需要根据配置来生成对应的数据结构,例如将如下配置中的路径 resources[].image.width
{
field: 'resources[].image.width',
value: 200
}
转换成如下的对象结构:
{
resources: [
{
image: {
width: 200
}
}
]
}
我们的路径 resources[].image.width 类似于 jsonpath,其中 resources[] 表示 resources 字段的值是一个数组,点字符(.) 前面的字段是一个 Object 对象,如 resources[].image 表示数组 resources 里存放的是有 image 属性的对象,image.width 则表示 image 是一个有width 属性的对象。
那么我们如何将路径转换成我们想要的对象呢?
借助栈实现
栈是一种 后进先出(LIFO)的数据结构,先添加或待删除的元素都保存在栈顶,我们就利用栈的后进先出的特性,将路径 resources[].image.width 根据 [] 或 . 分割成对象的 key 放入栈中,在栈顶的元素是我们想要转换的对象的最内层的 key,栈低的元素则是我们想要转换对象的最外层的key。我们将栈中的元素逐个弹出,根据 key 的类型(Object/Array) 转换成对应的对象结构。
// 栈
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element);
}
pop() {
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
clear() {
this.items = [];
}
size() {
return this.items.length;
}
print() {
// return this.items.toString()
return JSON.stringify(this.items)
}
}
/**
* 将 路径 转成对象key及对象类型(Object/Array)的对象 { dataKey: key, dataType: Object | Array }
* 将对象 { dataKey: key, dataType: Object | Array } 压入栈中
* @param {*} jsonpath 路径
*/
const pathStrPushToStack = (jsonpath) => {
const stack = new Stack()
const dot = '.'
let prevCharIndex = 0
let preChar = ''
let key = ''
for (let i = 0; i < jsonpath.length; i++) {
if (jsonpath[i] === dot) {
key = jsonpath.substring(prevCharIndex, i + 1).replace(/\./g, '')
prevCharIndex = i;
preChar = jsonpath[i]
if (key) {
stack.push({ dataType: 'Object', dataKey: key })
}
key = ''
} else if (jsonpath[i] === ']') {
key = jsonpath.substring(prevCharIndex, i + 1).replace(/(\.|\[|\])/g, '')
prevCharIndex = i + 1;
preChar = jsonpath[i]
if (key) {
stack.push({ dataType: 'Array', dataKey: key })
}
key = ''
} else {
key += jsonpath[i]
if (i === jsonpath.length - 1) {
stack.push({ dataType: preChar === dot ? 'Object' : "Array", dataKey: key })
}
}
}
return stack
}
/**
* 将 路径 转换成目标对象
* @param {*} jsonpath 路径
* @param {*} value 最内层字段需要设置的值
*/
const jsonpathToObj = (jsonpath, value) => {
// 将路径转换成{key, dataType} 对象放入栈中
const stack = pathStrPushToStack(jsonpath)
let resultObj = undefined
const size = stack.length
// 依次从栈中取出栈顶元素,转换成对象
for (let i = 0; i < size; i++) {
const stackEle = stack.pop()
if (stackEle.dataType === 'Object') {
if (!resultObj) {
resultObj = { [stackEle.dataKey]: value }
} else {
resultObj = { [stackEle.dataKey]: resultObj }
}
}
else if (stackEle.dataType === 'Array') {
resultObj = { [stackEle.dataKey]: [resultObj] }
}
}
return resultObj
}