EN
JavaScript - json difference visualization
7 points
In this article, we're going to have a look at how to write own function to compare and visualise JSONs. This article is composed with in two parts: json difference checking and table visualzisation.
Simple preview:
xxxxxxxxxx
1
{"name": "John", "items": [ 1 , 2, "3", {"name": "Item 1", "z": [4] }, 5 ]}
2
{"name": "John", "items": ["1", 2, 3 , {"name": "Item 2", "x": 3}, "a", "b"]}
3
// u u d d c d c c
4
// Where: u - updated, c - created, d - deleted

Note: read this article to see console print version.
This section presents simple logic that visualise difference of jsons in table.
xxxxxxxxxx
1
2
<html>
3
<style>
4
5
table { border-collapse: collapse; font-size: 13px; }
6
7
td { padding: 2px 4px; border: 1px solid; }
8
9
td.label { width: 40px; }
10
td.value { min-width: 50px; }
11
12
td.unchanged { border-color: #dddada; background: #ffffff; }
13
td.old { border-color: #ffa8a8; background: #ffd6d6; }
14
td.new { border-color: #92ca92; background: #e5ffe5; }
15
16
td table { width: 100% }
17
18
</style>
19
<script>
20
21
// -------------------------------------------------------------------
22
// difference calculator
23
24
function getType(object) {
25
return Object.prototype.toString.call(object);
26
}
27
28
function getDescription(type) {
29
if (type == '[object Array]') {
30
return 'array';
31
}
32
if (type == '[object Object]') {
33
return 'object';
34
}
35
return 'simple';
36
}
37
38
function createOldItem(name, status, type, value) {
39
return {
40
name: name || null,
41
status: status,
42
oldType: getDescription(type),
43
newType: null,
44
oldValue: value,
45
newValue: null,
46
};
47
}
48
49
function createNewItem(name, status, type, value) {
50
return {
51
name: name || null,
52
status: status,
53
oldType: null,
54
newType: getDescription(type),
55
oldValue: null,
56
newValue: value,
57
};
58
}
59
60
var createDeletedItem = function(key, type, value) {
61
return createOldItem(key, 'deleted', type, value);
62
};
63
64
var createCreatedItem = function(key, type, value) {
65
return createNewItem(key, 'created', type, value);
66
};
67
68
function coverContent(entity, createItem) {
69
var items = [ ];
70
for (const key in entity) {
71
if (entity.hasOwnProperty(key)) {
72
items.push(coverEntity(key, entity[key], createItem));
73
}
74
}
75
return items;
76
}
77
78
function coverEntity(name, entity, createItem) {
79
var type = getType(entity);
80
if (type === '[object Array]' || type === '[object Object]') {
81
var content = coverContent(entity, createItem);
82
return createItem(name, type, content);
83
} else {
84
return createItem(name, type, entity);
85
}
86
}
87
88
function compareContent(a, b) {
89
var items = [];
90
for (const key in a) {
91
if (a.hasOwnProperty(key)) {
92
if (b.hasOwnProperty(key)) {
93
items.push(compareEntries(a[key], b[key], key));
94
} else {
95
items.push(coverEntity(key, a[key], createDeletedItem));
96
}
97
}
98
}
99
for (const key in b) {
100
if (b.hasOwnProperty(key)) {
101
if(a.hasOwnProperty(key)){
102
continue;
103
}
104
items.push(coverEntity(key, b[key], createCreatedItem));
105
}
106
}
107
return items;
108
}
109
110
function compareEntries(a, b, name) {
111
var oldType = getType(a);
112
var newType = getType(b);
113
if (a === b) {
114
return createOldItem(name, 'unchanged', oldType, a);
115
}
116
if (oldType == newType) {
117
// both have array or object type:
118
if (oldType === '[object Array]' || oldType === '[object Object]') {
119
var oldValue = compareContent(a, b);
120
return createOldItem(name, 'unchanged', oldType, oldValue);
121
}
122
// both have simple type: boolean, number, string, etc.:
123
var description = getDescription(oldType);
124
return {
125
name: name || null,
126
status: 'updated',
127
oldType: description,
128
newType: description,
129
oldValue: a,
130
newValue: b,
131
};
132
} else {
133
// both have different types:
134
var oldDescription = getDescription(oldType);
135
var newDescription = getDescription(newType);
136
return {
137
name: name || null,
138
status: 'updated',
139
oldType: oldDescription,
140
newType: newDescription,
141
oldValue: (oldDescription == 'simple' ? a : coverContent(a, createDeletedItem)),
142
newValue: (newDescription == 'simple' ? b : coverContent(b, createCreatedItem)),
143
};
144
}
145
}
146
147
// -------------------------------------------------------------------
148
// difference visualization
149
150
var rules = [
151
{expression: /&/g, replacement: '&' }, // keep this rule at first position
152
{expression: /</g, replacement: '<' },
153
{expression: />/g, replacement: '>' },
154
{expression: /"/g, replacement: '"'},
155
{expression: /'/g, replacement: '''}
156
];
157
158
function escapeValue(value) {
159
if(value == null) {
160
return null;
161
}
162
var result = String(value);
163
for (var i = 0; i < rules.length; ++i) {
164
var rule = rules[i];
165
result = result.replace(rule.expression, rule.replacement);
166
}
167
return result;
168
};
169
170
function createRow(type, name, value) {
171
var html = '<tr class="' + type + '">';
172
if (name) {
173
html += ' <td class="label ' + type + '">' + name + '</td>';
174
}
175
html += ' <td class="value ' + type + '">' + value + '</td>';
176
html += '</tr>';
177
return html;
178
}
179
180
function createNode(node) {
181
var html = '';
182
var name = escapeValue(node.name);
183
if (node.oldType) {
184
var oldValue = (node.oldType == 'simple' ? escapeValue(node.oldValue) : createTable(node.oldValue));
185
if (node.status == 'unchanged') {
186
return createRow('unchanged', name, oldValue);
187
}
188
html += createRow('old', name, oldValue);
189
}
190
if (node.newType) {
191
var newValue = node.newType == 'simple' ? escapeValue(node.newValue) : createTable(node.newValue);
192
html += createRow('new', name, newValue);
193
}
194
return html;
195
}
196
197
function createRows(nodes) {
198
var html = '';
199
for (var i = 0; i < nodes.length; ++i) {
200
html += createNode(nodes[i]);
201
}
202
return html;
203
}
204
205
function createTable(nodes) {
206
var html = '<table>' +
207
' <tbody>' +
208
createRows(nodes) +
209
' </tbody>' +
210
'</table>';
211
return html;
212
}
213
214
function createReport(difference) {
215
var html = '<table>' +
216
' <tbody>' +
217
createNode(difference) +
218
' </tbody>' +
219
'</table>';
220
return html;
221
}
222
223
function showDifference(difference) {
224
document.body.innerHTML = createReport(difference);
225
}
226
227
</script>
228
<body>
229
<script>
230
231
function compareJsons(a, b) {
232
var aObject = JSON.parse(a);
233
var bObject = JSON.parse(b);
234
var difference = compareEntries(aObject, bObject);
235
showDifference(difference);
236
}
237
238
var json1 = '{"name":"John","items":[1,2,"3",{"name":"Item 1","z":[4]},5]}';
239
var json2 = '{"name":"John","items":["1",2,3,{"name":"Item 2","x":3},"a","b"]}';
240
241
compareJsons(json1, json2);
242
243
</script>
244
</body>
245
</html>