一、 前言

“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. 一步一步带你在VS 2017中配置OpenGL

    在VS2017环境中配置OpenGL,我们分三步:配置GLFW.配置GLAD.导出项目模板. 配置GLFW 1.首先下载GLFW,点击这里,进入Github下载. 或者 点击这里从百度云下载,提取码为 ...

  2. Java 学习笔记之 线程脏读

    线程脏读: 发生脏读的情况是在读取实例变量时,值已经被其他线程更改过了. public class DirtyReadVar { public String username = "A&qu ...

  3. 搭建docker+swoole+php7 的环境

    最近在学习swoole php扩展,苦恼于其运行环境不能在win系统下运行, 但开发代码一直在win系统上,很无奈,,,, 所以就用docker来代替,舒服~ 有很多相关docker的swoole镜像 ...

  4. vue路由跳转的方式

    vue路由跳转有四种方式 1. router-link 2. this.$router.push() (函数里面调用) 3. this.$router.replace() (用法同push) 4. t ...

  5. sech和asech--双曲正割和反双曲正割函数

    sech和asech--双曲正割和反双曲正割函数 [功能简介]求变量的双曲正割和反双曲正割. [语法格式] 1.Y=sech(X) 计算X的双曲正割,sech(x)=1/cosh(x).X可以为向量. ...

  6. LeetCode_682-Baseball Game

    给定一个字符串列表,字符串包含整数,’+’,’D’,’C’,整数代表一个分数,’+’代表后两个有效分数的和,’D’代表后一个有效分数的两倍,’C’代表删除后一个有效的分数值,最后求所有有效分数的和.例 ...

  7. PowerBI系列之入门案例动态销售报告

    本文将讲解如何从零开始使用PowerBI Desktop制作一份动态销售报告.帮助大家快速入门PowerBI Desktop的操作.我们先来看一下一份动态销售报告的构成. 1.左上角放置了小黎子数据分 ...

  8. SVN应用

    一:从服务器上down资料 1.在电脑上安装SVN客户端 2.在电脑本地创建个文件夹作为版本库 3.进入xfssvn文件夹右击鼠标选择SVN Checkout或SVN Update 4.输入服务器中配 ...

  9. 数据结构5_java---二叉树,树的建立,树的先序、中序、后序遍历(递归和非递归算法),层次遍历(广度优先遍历),深度优先遍历,树的深度(递归算法)

    1.二叉树的建立 首先,定义数组存储树的data,然后使用list集合将所有的二叉树结点都包含进去,最后给每个父亲结点赋予左右孩子. 需要注意的是:最后一个父亲结点需要单独处理 public stat ...

  10. vue-cli2、vue-cli3脚手架详细讲解

    前言: vue脚手架指的是vue-cli它是vue官方提供的一个快速构建单页面(SPA)环境配置的工具,cli 就是(command-line-interface  ) 命令行界面 .vue-cli是 ...