Languages
[Edit]
EN

JavaScript - json difference visualization

4 points
Created by:
JustMike
3491

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
JSONs difference visualisation with table in JavaScript
JSONs difference visualisation with table in JavaScript

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: '&amp;'  }, // keep this rule at first position
      { expression: /</g, replacement: '&lt;'   },
      { expression: />/g, replacement: '&gt;'   },
      { expression: /"/g, replacement: '&quot;' },
      { expression: /'/g, replacement: '&#039;' }
    ];

    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>

 

Hey 👋
Would you like to know what we do?
  • Dirask is a friendly IT community for learners, professionals and hobbyists to share their knowledge and help each other in extraordinary easy way.
  • We welcome everyone,
    no matter what the experience,
    no matter how basic the question is,
    this community will help you.