一、 前言

“JSON对象合并”是前端开发和 NodeJS 环境开发中非常常见的操作。开发者通常会通过循环遍历或一些库封装的方法或 JavaScript ECMAScript 2015 定义的 Object.assign() 来实现。

二、 常见合并方式

1.    方法一:循环遍历法

function extend() {
var length = arguments.length;
if(length == 0)return {};
if(length == 1)return arguments[0];
var target = arguments[0] || {};
for (var i = 1; i < length; i++) {
var source = arguments[i];
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
} return target;
}
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4, d: 5 };
const result = extend(obj1, obj2);
console.log(result);//{ a: 1, b: 3, c: 4, d: 5 }

2.    方法二:Object.assign()

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4, d: 5 };
const result = Object.assign(obj1, obj2);
console.log(result);//{ a: 1, b: 3, c: 4, d: 5 }

3.    方法三:用jQuery插件

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const { window } = new JSDOM(`<!DOCTYPE html>`);
const $ = require('jQuery')(window);
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4, d: 5 };
const result = $.extend(obj1, obj2);
console.log(result);//{ a: 1, b: 3, c: 4, d: 5 }

从测试结果来看,以上三种方案都能得到预期结果。

三、 进阶

但是在实际项目中,需要合并的对象往往不是那么简单,比如合并以下几个对象:

let chartScheme = {
series: [{
name: '本地',
type: 'bar',
stack: '驻留分析',
},
{
name: '省内',
type: 'bar',
stack: '驻留分析',
}
]
};
let chartStyle = {
grid: {
top: '25%',
bottom: '30',
left: '15%',
right: '5%'
},
series: [{
barWidth: '10px',
itemStyle: {
color: '#4AC6E7'
}
},
{
barWidth: '10px',
itemStyle: {
color: '#73C642'
}
}
]
};
let chartStyle2 = {
series: [{
lineStyle: {
normal: {
color: '#36d0f3'
}
},
itemStyle: {
normal: {
color: '#36d0f3'
}
}
},
{
lineStyle: {
normal: {
color: '#36d0f3'
}
},
itemStyle: {
normal: {
color: '#36d0f3'
}
}
}
]
};
let chartData = {
series: [{
data: [35, 60, 50, 80, 80, 55, 70, 70],
},
{
data: [60, 80, 60, 45, 80, 55, 60, 20],
}
]
};

合并的目标是保留所有非同名属性,同名原子属性用后面的参数覆盖前面的参数。

下面用上述几种方法做合并测试:

1.    测试方法一

let result = extend(chartScheme, chartStyle, chartStyle2, chartData);
console.log(result);

结果如下,非原子属性被覆盖,未达到要求:

result = {
"series": [{
"data": [35, 60, 50, 80, 80, 55, 70, 70]
}, {
"data": [60, 80, 60, 45, 80, 55, 60, 20]
}],
"grid": {
"top": "25%",
"bottom": "30",
"left": "15%",
"right": "5%"
}
}

2.    测试方法二

let result = Object.assign(chartScheme, chartStyle, chartStyle2, chartData);
console.log(result);

结果如下,跟方法一一样未达到要求:

result = {
"series": [{
"data": [35, 60, 50, 80, 80, 55, 70, 70]
}, {
"data": [60, 80, 60, 45, 80, 55, 60, 20]
}],
"grid": {
"top": "25%",
"bottom": "30",
"left": "15%",
"right": "5%"
}
}

3.    测试方法三

let result = $.extend(chartScheme, chartStyle, chartStyle2, chartData);
console.log(JSON.stringify(result));

结果如下,还是没有到要求:

result = {
"series": [{
"data": [35, 60, 50, 80, 80, 55, 70, 70]
}, {
"data": [60, 80, 60, 45, 80, 55, 60, 20]
}],
"grid": {
"top": "25%",
"bottom": "30",
"left": "15%",
"right": "5%"
}
}

很显然,以上三种方法在合并比较复杂的对象时都是有缺陷的,合并后目标对象的非原子属性被后面的源对象的同名属性直接覆盖了。

不过 jQuery 的“extend()”方法有两个原型:

$.extend( target [, object1 ] [, objectN ] )
$.extend( [deep ], target, object1 [, objectN ] )

上面用的是第一个原型,第二个原型还有一个深拷贝选项,即“$.extend()”的第一个参数传“true”,可以试试:

let result = $.extend(true, chartScheme, chartStyle, chartStyle2, chartData);
console.log(JSON.stringify(result));
 

结果如下:

let result = $.extend(true, chartScheme, chartStyle, chartStyle2, chartData);
console.log(JSON.stringify(result));
result = {
"series": [{
"name": "本地",
"type": "bar",
"stack": "驻留分析",
"barWidth": "10px",
"itemStyle": {
"color": "#4AC6E7",
"normal": {
"color": "#36d0f3"
}
},
"lineStyle": {
"normal": {
"color": "#36d0f3"
}
},
"data": [35, 60, 50, 80, 80, 55, 70, 70]
}, {
"name": "省内",
"type": "bar",
"stack": "驻留分析",
"barWidth": "10px",
"itemStyle": {
"color": "#73C642",
"normal": {
"color": "#36d0f3"
}
},
"lineStyle": {
"normal": {
"color": "#36d0f3"
}
},
"data": [60, 80, 60, 45, 80, 55, 60, 20]
}],
"grid": {
"top": "25%",
"bottom": "30",
"left": "15%",
"right": "5%"
}
}
 

从结果来看,合并后的数据是完整的,并且合并后再修改源对象,并没有影响到合并结果,这说明合并拷贝是深拷贝,并不是引用拷贝,是一款不错的 API。但由于 jQuery 库在 node 环境使用还是比较麻烦的,在SPA 应用和移动端的效率也不太高,因此有必要整理一个较高效的库供日常使用。

四、 高效可靠的合并方法

1.    关键代码

经过对数个前端 js 库的对比研究,整理了一个库,关键代码如下:

/**
* @Description 克隆对象
* 能被克隆的对象类型:
* Plain object, Array, TypedArray, number, string, null, undefined.
* 直接用原始数据进行赋值的数据类型:
* BUILTIN_OBJECT
* 用户定义类的实例将克隆到一个原型中没有属性的普通对象。
* @method clone
* @param {*} source
* @return {*} new
*/
clone(source) {
if (source == null || typeof source !== 'object') {
return source;
}
var result = source;
var typeStr = this.objToString.call(source);
if (typeStr === '[object Date]') {
result = this.cloneDate(source);
}
else if (typeStr === '[object RegExp]') {
result = this.cloneRegExp(source);
}
else if (typeStr === '[object Function]') {
result = this.cloneFunction(source);
}
else if (typeStr === '[object Array]') {
result = [];
for (var i = 0, len = source.length; i < len; i++) {
result[i] = this.clone(source[i]);
}
}
else if (this.TYPED_ARRAY[typeStr]) {
var Ctor = source.constructor;
if (source.constructor.from) {
result = Ctor.from(source);
}
else {
result = new Ctor(source.length);
for (var i = 0, len = source.length; i < len; i++) {
result[i] = this.clone(source[i]);
}
}
}
else if (!this.BUILTIN_OBJECT[typeStr] && !this.isDom(source)) {
result = {};
for (var key in source) {
if (this.hasOwn(source, key)) {
result[key] = this.clone(source[key]);
}
}
}
return result;
},
/**
* @Description 合并函数
* @method merge
* @param {*} target
* @param {*} source
* @param {boolean} [overwrite=false]
* @return {Object}
*/
merge(target, source, overwrite) {
// We should escapse that source is string
// and enter for ... in ...
if (!this.isObject(source) || !this.isObject(target)) {
return overwrite ? this.clone(source) : target;
}
for (var key in source) {
if (this.hasOwn(source, key)) {
var targetProp = target[key];
var sourceProp = source[key];
if (this.isObject(sourceProp)
&& this.isObject(targetProp)
&& !this.isDom(sourceProp)
&& !this.isDom(targetProp)
&& !this.isBuiltInObject(sourceProp)
&& !this.isBuiltInObject(targetProp)
) {
// 如果需要递归覆盖,就递归调用merge
this.merge(targetProp, sourceProp, overwrite);
}
else if (overwrite || !(key in target)) {
// 否则只处理overwrite为true,或者在目标对象中没有此属性的情况
// NOTE,在 target[key] 不存在的时候也是直接覆盖
target[key] = this.clone(source[key], true);
}
}
}
return target;
}

2.    完整代码与库

完整代码在 http://192.168.x.y/z/merge-util,代码已经经过处理,兼容 node 环境和浏览器环境,npm 包已经发布到公司内部库:http://192.168.x.y:z/#browse/browse:merge-util

五、 使用方法

1.    Node环境

使用 npm 命令用临时 registry 安装包:

npm --registry http://192.168.x.y:z/my-repository/ install merge-util

或设置持久 registry 再安装包:

npm config set registry http://192.168.x.y:z/my-repository/
npm install merge-util
const mergeUtil = require('merge-util');
let result = mergeUtil.mergeAll([{},chartScheme, chartStyle, chartStyle2, chartData], true);
console.log(JSON.stringify( result));

2.    浏览器环境

通过上述 npm 命令将安装包下载到本地,或从 github 下载 index.js 文件,然后在 html 文件里引用即可使用:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Merge-test</title>
<script src="./rs-merge-util.js"></script>
<script src="./testdata.js"></script>
</head>
<body>
<script>
const result = mergeUtil.mergeAll([{}, chartScheme, chartStyle, chartStyle2, chartData0, chartData], true);
console.log(JSON.stringify(result));
</script>
</body>
</html>
 

合并JSON对象的正确方式的更多相关文章

  1. jq 合并json对象

    一,保存object1和2合并后产生新对象,若2中有与1相同的key,默认2将会覆盖1的值 1 var object = $.extend({}, object1, object2); 二,将2的值合 ...

  2. JSON字符串转换为JSON对象

    一.JSON字符串转换为JSON对象 A:eval函数 eval函数可以直接将本质符合或者近似符合JSON格式的字符串转换为JSON对象,使用方式如: eval('(' + str + ')'); / ...

  3. json对象和json字符串转换方法

    在WEB数据传输过程中,json是以文本,即字符串的轻量级形式传递的,而客户端一般用JS操作的是接收到的JSON对象,所以,JSON对象和JSON字符串之间的相互转换.JSON数据的解析是关键. 先明 ...

  4. 自定义 JSON 对象

    针对 IE9 以下不支持 JSON 对象的处理方式,网上大部分自定义的方式无形之中都会将中文转码为 Unicode 编码格式的字符换,但是在浏览器中我们有无法察觉到(浏览器自己解析成 UTF8 了), ...

  5. MVC中用ajax提交json对象数组

    应用场景:在前端用ajax向服务器提交json对象数组,在controller的以对象数组作为函数的参数,提交的json数组直接转为服务器端的对象数组. 如: 要将json对象数组[{Id:1,Nam ...

  6. js如何创建JSON对象

    js如何创建JSON对象 一.总结 一句话总结:直接创建js数组和js对象即可,然后JSON.stringify就可以获取json字符串,js中的一切都是对象,而且js中的对象都是json对象 js ...

  7. 字符串和JSON对象互转的方法

    采用Ajax的项目开发过程中,经常需要将JSON格式的字符串返回到前端,前端解析成JS对象(JSON ).字符串转JSON对象 1.eval方式解析.function strToJson(str){ ...

  8. pageParam要求传个JSON对象的方法

    pageParam要求传个JSON对象,使用方式:api.openWin({name: 'page1',url: 'page1.html',pageParam: {x: '1000',y: '2000 ...

  9. Json对象和字符串互相转换 数据拼接 JSON使用方式

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. 一.JSON字符串转换为JSON对象: eval() 和 JSON.parse eg- json字符串: ...

随机推荐

  1. Scala 学习笔记之函数(1)

    class Student { val mySayHello1: String => Unit = sayHello val mySayHello2: (String, Int) => U ...

  2. 如何决定使用 HashMap 还是 TreeMap?

    问:如何决定使用 HashMap 还是 TreeMap? 介绍 TreeMap<K,V>的Key值是要求实现java.lang.Comparable,所以迭代的时候TreeMap默认是按照 ...

  3. JS时间格式CST转GMT

    转换方法:JS 时间格式CST转GMT 时区和Date:Java中的Date和时区转换

  4. Network in Network(2013),1x1卷积与Global Average Pooling

    目录 写在前面 mlpconv layer实现 Global Average Pooling 网络结构 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 <Net ...

  5. C语言I博客作业03

    这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/CST2019-2/homework/8717 我在这个课程的目 ...

  6. (7)Cmake的使用简介

        CMake是一个跨平台的安装(编译)工具,是一个比Make更高级的的编译配置工具,可以根据不同平台.不同编译器,通过编写CmakeLists,可以控制生成的Makefile,从而控制编译过程. ...

  7. 一文掌握在Word中快速编写公式

    在使用Word编写文章时,总会遇到书写数学公式的情况.使用Word的公式输入工具需要频繁地使用鼠标,因而编写公式会显得繁琐麻烦,那么有什么办法可以优雅地在Word中书写公式呢?其实Word早在Word ...

  8. 朋友外包干了5年java,居然不知道dubbo-monitor是怎么用的?

    Dubbo工具--dubbo-monitor监控平台的发布和使用 1)下载 https://github.com/alibaba/dubbo/archive/dubbo-2.5.8.zip 2)编译 ...

  9. 代码审计准备之Thinkphp3

    0x01环境部署: 下载: 获取ThinkPHP的方式很多,官方网站(http://thinkphp.cn)是最好的下载和文档获取来源. 官网提供了稳定版本的下载:http://thinkphp.cn ...

  10. openresty性能测试报告分析

    一.openresty介绍 1.什么是openresty 通过揉和众多设计良好的 Nginx 模块,OpenResty 有效地把 Nginx 服务器转变为一个强大的 Web 应用服务器,基于它开发人员 ...