先别吐槽标题,咱们今天讲点 JSON.stringify 不常用的操作。
看个例子
const data = {
global: {
preview: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p1.png'
},
layouts: [
{
elements: [
{
imageUrl: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p2.png',
elements: [
{
imageUrl: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p3.png'
}
]
},
{
url: 'https://bucket2.oss-cn-hangzhou.aliyuncs.com/images/p2.png'
}
],
backgroundImage: 'https://bucket.oss-cn-hangzhou.aliyuncs.com/images/p4.png'
}
]
};
你同往常一样从接口拿到的模板数据将图片展示到页面上,某天后端同学为 oss 资源加了个 CDN 域名后需要让原本走 bucket.oss-cn-hangzhou.aliyuncs.com
的图片统一走 some.oss-cdn.com
加速图片资源的访问,这时你需要将数据中的所有图片资源的域名 bucket.oss-cn-hangzhou.aliyuncs.com
改成 cdn 域名 some.oss-cdn.com
。
知道数据结构情况我们当然可以直接遍历整个对象:
function replace(url) {
return url.replace('bucket.oss-cn-hangzhou.aliyuncs.com', 'some.oss-cdn.com');
}
function replaceOssHost(data) {
data.global.preview = replace(data.global.preview);
data.layouts.forEach(layout => {
layout.backgroundImage = replace(layout.backgroundImage);
layout.elements.forEach(element => {
element.url = replace(element.url);
element.imageUrl = replace(element.imageUrl);
if (element.elements) {
// ...
}
});
});
return data;
}
嗯,一看就很不靠谱!随便加一个新的 image 资源字段,遍历的规则就得改了,所以我们需要一个与具体解构无关的替换方法,大概如下:
function replaceOssHost(data) {
Object.entries(data).forEach(([k, v]) => {
if (v && typeof v === 'object') {
return replaceOssHost(v);
}
if (typeof v === 'string') {
data[k] = replace(v);
}
});
return data;
}
这不就是递归遍历一个对象么?所以和 JSON.stringify
有啥关系?
常被忽略 JSON.stringify 参数
问题:你知道 JSON.stringify
和 JSON.parse
方法的第二个参数吗?
不清楚的小伙伴不妨可以看下 MDN 的文档:
-
MDN JSON.parse
-
MDN JSON.stringify
JSON.stringify(value[, replacer [, space]])
JSON.parse(text[, reviver])
简单来讲就是 JSON.parse 和 JSON.stringify 都提供了第二个可选参数:
- 可以
key
value
的形式遍历当前对象所有可遍历属性。 - 支持返回新的值替换当前属性的值。
然后 JSON.parse 和 JSON.stringify 遍历顺序有些区别:
const data = {
"1": 1,
"2": 2,
"3": {
"4": 4,
"5": {
"6": 6
}
}
};
const dataStr = JSON.stringify(data);
JSON.stringify(data, function (k, v) {
console.log(`${k}:`, v);
return v;
});
// 输出:根对象 k 为空,遍历从外层往内层遍历
// : { '1': 1, '2': 2, '3': { '4': 4, '5': { '6': 6 } } } 对象自身时的 key 会是空的
// 1: 1
// 2: 2
// 3: { '4': 4, '5': { '6': 6 } }
// 4: 4
// 5: { '6': 6 }
// 6: 6
JSON.parse(dataStr, function (k, v) {
console.log(`${k}:`, v);
return v;
});
// 输出:根对象 k 为空,从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身
// 1: 1
// 2: 2
// 4: 4
// 6: 6
// 5: { '6': 6 }
// 3: { '4': 4, '5': { '6': 6 } }
// : { '1': 1, '2': 2, '3': { '4': 4, '5': { '6': 6 } } } 对象自身时的 key 会是空的
JSON.stringify 的遍历会更符合直觉一点,JSON.parse 遍历属性为对象时会优先从对象的子级开始遍历,大多时候我们并不太关心遍历顺序,但需要注意一下遍历对象自身时的 key 会是空的。
看回原来的例子,使用 JSON.stringify 可以十分方便实现 host 的替换:
使用 JSON.stringify 进行对象遍历
简单几行就可以实现替换的操作:
function replaceOssHost(data) {
return JSON.parse(JSON.stringify(data, function (k, v) {
if (v && typeof v === 'string') {
return replace(v);
}
return v;
}));
}
当然这是比较简单的例子,下面看个稍微复杂的例子:
const data = {
global: {
preview: 'data:image/png;base64,iVBORw0KGgoAAAANSUhE...'
},
layouts: [
{
elements: [
{
imageUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhE...',
elements: [
{
imageUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhE...'
}
]
},
{
url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhE...'
}
],
backgroundImage: 'data:image/png;base64,iVBORw0KGgoAAAANSUhE...'
}
]
};
同样是图片资源,但是资源变成了 base64 data,而且现在要求在保存模板数据数据时需要将 base64 资源上传到 oss 并替换成对应的链接。
base64 上传是个异步操作,JSON.stringify 的 replacer 是个同步函数,我们无法直接在 replacer 中进行上传和图片替换,改如何处理呢?
实现其实很简单,将上传和替换分离就好了,直接上代码了:
const data = {
global: {
preview: 'data:image/png;base64,iVBORw0KGgoAAAANSUhE...'
}
};
// 遍历返回资源 map
function getResources(data) {
const resources = new Map();
const content = JSON.stringify(data, function (_, v) {
if (v && typeof v === 'string' && v.includes('base64,')) {
resources.set(v, v);
}
return v;
});
return { content, resources };
}
// base64 资源上传
function uploadBase64(base64) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('https://some.oss-cdn.com/images/xxx.png');
}, 200);
});
}
async function uploadResources(data) {
const { content, resources } = getResources(data);
// 上传替换 base64 为 url
for (let [_, value] of resources) {
const url = await uploadBase64(value);
resources.set(value, url);
}
// 在根据 resources value 和 url 映射替换 base64 为 url
return JSON.parse(content, function(_, v) {
if (!v || typeof v !== 'string' || !v.includes('base64,')) {
return v;
}
return resources.get(v) || v;
});
}
之前很少关注 JSON.stringify
和 JSON.parse
的参数细节,但在特定业务场景下却有奇效,下次遇到了需要递归数据操作时不妨试试先 JSON.stringify
和 JSON.parse
。
over~!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!