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:
{"name": "John", "items": [ 1 , 2, "3", {"name": "Item 1", "z": [4] }, 5 ]}
{"name": "John", "items": ["1", 2, 3 , {"name": "Item 2", "x": 3}, "a", "b"]}
// u u d d c d c c
// Where: u - updated, c - created, d - deleted

Note: read this article to see console print version.
Run below example to see details.
1. Custom json difference visualization
This section presents simple logic that visualise difference of jsons in table.
// ONLINE-RUNNER:browser;
<!doctype html>
<html>
<style>
table { border-collapse: collapse; font-size: 13px; }
td { padding: 2px 4px; border: 1px solid; }
td.label { width: 40px; }
td.value { min-width: 50px; }
td.unchanged { border-color: #dddada; background: #ffffff; }
td.old { border-color: #ffa8a8; background: #ffd6d6; }
td.new { border-color: #92ca92; background: #e5ffe5; }
td table { width: 100% }
</style>
<script>
// -------------------------------------------------------------------
// difference calculator
function getType(object) {
return Object.prototype.toString.call(object);
}
function getDescription(type) {
if (type == '[object Array]') {
return 'array';
}
if (type == '[object Object]') {
return 'object';
}
return 'simple';
}
function createOldItem(name, status, type, value) {
return {
name: name || null,
status: status,
oldType: getDescription(type),
newType: null,
oldValue: value,
newValue: null,
};
}
function createNewItem(name, status, type, value) {
return {
name: name || null,
status: status,
oldType: null,
newType: getDescription(type),
oldValue: null,
newValue: value,
};
}
var createDeletedItem = function(key, type, value) {
return createOldItem(key, 'deleted', type, value);
};
var createCreatedItem = function(key, type, value) {
return createNewItem(key, 'created', type, value);
};
function coverContent(entity, createItem) {
var items = [ ];
for (const key in entity) {
if (entity.hasOwnProperty(key)) {
items.push(coverEntity(key, entity[key], createItem));
}
}
return items;
}
function coverEntity(name, entity, createItem) {
var type = getType(entity);
if (type === '[object Array]' || type === '[object Object]') {
var content = coverContent(entity, createItem);
return createItem(name, type, content);
} else {
return createItem(name, type, entity);
}
}
function compareContent(a, b) {
var items = [ ];
for (const key in a) {
if (a.hasOwnProperty(key)) {
if (b.hasOwnProperty(key)) {
items.push(compareEntries(a[key], b[key], key));
} else {
items.push(coverEntity(key, a[key], createDeletedItem));
}
}
}
for (const key in b) {
if (b.hasOwnProperty(key)) {
if(a.hasOwnProperty(key)){
continue;
}
items.push(coverEntity(key, b[key], createCreatedItem));
}
}
return items;
}
function compareEntries(a, b, name) {
var oldType = getType(a);
var newType = getType(b);
if (a === b) {
return createOldItem(name, 'unchanged', oldType, a);
}
if (oldType == newType) {
// both have array or object type:
if (oldType === '[object Array]' || oldType === '[object Object]') {
var oldValue = compareContent(a, b);
return createOldItem(name, 'unchanged', oldType, oldValue);
}
// both have simple type: boolean, number, string, etc.:
var description = getDescription(oldType);
return {
name: name || null,
status: 'updated',
oldType: description,
newType: description,
oldValue: a,
newValue: b,
};
} else {
// both have different types:
var oldDescription = getDescription(oldType);
var newDescription = getDescription(newType);
return {
name: name || null,
status: 'updated',
oldType: oldDescription,
newType: newDescription,
oldValue: (oldDescription == 'simple' ? a : coverContent(a, createDeletedItem)),
newValue: (newDescription == 'simple' ? b : coverContent(b, createCreatedItem)),
};
}
}
// -------------------------------------------------------------------
// difference visualization
var rules = [
{ expression: /&/g, replacement: '&' }, // keep this rule at first position
{ expression: /</g, replacement: '<' },
{ expression: />/g, replacement: '>' },
{ expression: /"/g, replacement: '"' },
{ expression: /'/g, replacement: ''' }
];
function escapeValue(value) {
if(value == null) {
return null;
}
var result = String(value);
for (var i = 0; i < rules.length; ++i) {
var rule = rules[i];
result = result.replace(rule.expression, rule.replacement);
}
return result;
};
function createRow(type, name, value) {
var html = '<tr class="' + type + '">';
if (name) {
html += ' <td class="label ' + type + '">' + name + '</td>';
}
html += ' <td class="value ' + type + '">' + value + '</td>';
html += '</tr>';
return html;
}
function createNode(node) {
var html = '';
var name = escapeValue(node.name);
if (node.oldType) {
var oldValue = (node.oldType == 'simple' ? escapeValue(node.oldValue) : createTable(node.oldValue));
if (node.status == 'unchanged') {
return createRow('unchanged', name, oldValue);
}
html += createRow('old', name, oldValue);
}
if (node.newType) {
var newValue = node.newType == 'simple' ? escapeValue(node.newValue) : createTable(node.newValue);
html += createRow('new', name, newValue);
}
return html;
}
function createRows(nodes) {
var html = '';
for (var i = 0; i < nodes.length; ++i) {
html += createNode(nodes[i]);
}
return html;
}
function createTable(nodes) {
var html = '' +
'<table>' +
' <tbody>' +
createRows(nodes) +
' </tbody>' +
'</table>';
return html;
}
function createReport(difference) {
var html = '' +
'<table>' +
' <tbody>' +
createNode(difference) +
' </tbody>' +
'</table>';
return html;
}
function showDifference(difference) {
document.body.innerHTML = createReport(difference);
}
</script>
<body>
<script>
function compareJsons(a, b) {
var aObject = JSON.parse(a);
var bObject = JSON.parse(b);
var difference = compareEntries(aObject, bObject);
showDifference(difference);
}
var json1 = '{"name":"John","items":[1,2,"3",{"name":"Item 1","z":[4]},5]}';
var json2 = '{"name":"John","items":["1",2,3,{"name":"Item 2","x":3},"a","b"]}';
compareJsons(json1, json2);
</script>
</body>
</html>