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.
Custom solution
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>