Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elisions: Add for objects and custom localization #109

Open
unclechu opened this issue Mar 15, 2023 · 0 comments
Open

Elisions: Add for objects and custom localization #109

unclechu opened this issue Mar 15, 2023 · 0 comments

Comments

@unclechu
Copy link

unclechu commented Mar 15, 2023

Hi, we forked your library in order to make a React component based on it:

We made a couple of changes to the original library behavior in relation to elisions rendering you might be also interested in adding to back to your library:

  1. Rendering elisions for objects the similar way they are rendered for arrays. For our use case we found it confusing that the untouched keys are just not showing up in the diff like they didn’t exist. And we though it would be very intuitive to render the placeholders/elisions for extra untouched fields the same way it’s already done for arrays.

    We added an extra option showElisionsForObjects which is a boolean we keep turned on by default. Setting it to false allows to use the original behavior of json-diff.

  2. Customizable elisions renderer. The “entries” word is hard-coded into the elision template. We though it would be nice to have it customizable in case anyone would need to add a localization other than English. So we added a generic way of customizing the elisions renderer by providing a renderer function.

    Custom renderer takes the elisions counter value, and just forwarded maxElisions value for convenience which can be just ignored. It can return a list of strings which will translate into multiple lines. Or just a single string which will transform into a single line (the same can be achieved by providing a list of one single string).

    renderElision?: (elisionCount: number, maxElisions: number) => string | string[];

Here is a patch with the essentials of the change (the patch has a couple of unrelated lines for the piece of React-related code, just ignore them): json-diff-react-elisions.patch.zip

The patch is produced by this command ran against https://github.com/relex/json-diff-react repo:

git diff 6246112 3077bf9 src/JsonDiff/Internal/json-diff.js src/JsonDiff/Internal/colorize.jsx src/JsonDiff/Internal/utils.ts 

The patch contents:

diff --git a/src/JsonDiff/Internal/colorize.jsx b/src/JsonDiff/Internal/colorize.jsx
index 7ba5ccd..c29bc50 100644
--- a/src/JsonDiff/Internal/colorize.jsx
+++ b/src/JsonDiff/Internal/colorize.jsx
@@ -3,7 +3,7 @@
 // The 'colorize' function was adapted from console rendering to browser
 // rendering - it now returns a JSX.Element.
 
-import { extendedTypeOf } from './utils';
+import { extendedTypeOf, elisionMarker } from './utils';
 import React from 'react';
 
 const subcolorizeToCallback = function (options, key, diff, output, color, indent) {
@@ -11,14 +11,20 @@ const subcolorizeToCallback = function (options, key, diff, output, color, inden
   const prefix = key ? `${key}: ` : '';
   const subindent = indent + '  ';
 
+  const maxElisions = options.maxElisions === undefined ? Infinity : options.maxElisions;
+
+  const renderElision =
+    options.renderElision ??
+    ((n, max) => (n < max ? [...Array(n)].map(() => '...') : `... (${n} entries)`));
+
   const outputElisions = (n) => {
-    const maxElisions = options.maxElisions === undefined ? Infinity : options.maxElisions;
-    if (n < maxElisions) {
-      for (let i = 0; i < n; i++) {
-        output(' ', subindent + '...');
-      }
+    const elisions = renderElision(n, maxElisions);
+    if (typeof elisions === 'string') {
+      output(' ', subindent + elisions);
     } else {
-      output(' ', subindent + `... (${n} entries)`);
+      elisions.forEach((x) => {
+        output(' ', subindent + x);
+      });
     }
   };
 
@@ -29,9 +35,23 @@ const subcolorizeToCallback = function (options, key, diff, output, color, inden
         return subcolorizeToCallback(options, key, diff.__new, output, '+', indent);
       } else {
         output(color, `${indent}${prefix}{`);
+
+        // Elisions are added in “json-diff” module depending on the option.
+        let elisionCount = 0;
+
         for (const subkey of Object.keys(diff)) {
           let m;
           subvalue = diff[subkey];
+
+          // Handle elisions
+          if (subvalue === elisionMarker) {
+            elisionCount++;
+            continue;
+          } else if (elisionCount > 0) {
+            outputElisions(elisionCount);
+            elisionCount = 0;
+          }
+
           if ((m = subkey.match(/^(.*)__deleted$/))) {
             subcolorizeToCallback(options, m[1], subvalue, output, '-', subindent);
           } else if ((m = subkey.match(/^(.*)__added$/))) {
@@ -40,6 +60,10 @@ const subcolorizeToCallback = function (options, key, diff, output, color, inden
             subcolorizeToCallback(options, subkey, subvalue, output, color, subindent);
           }
         }
+
+        // Handle elisions
+        if (elisionCount > 0) outputElisions(elisionCount);
+
         return output(color, `${indent}}`);
       }
 
@@ -105,10 +129,10 @@ const colorizeToCallback = (diff, options, output) =>
 export const colorize = function (diff, options = {}, customization) {
   const output = [];
 
-  let className;
-  let style;
-
   colorizeToCallback(diff, options, function (color, line) {
+    let className;
+    let style;
+
     if (color === ' ') {
       className = customization.unchangedClassName;
       style = customization.unchangedLineStyle;
@@ -122,7 +146,7 @@ export const colorize = function (diff, options = {}, customization) {
 
     let renderedLine = (
       <div className={className} style={style} key={output.length}>
-        {line + '\r\n'}
+        {line}
       </div>
     );
 
@@ -131,7 +155,7 @@ export const colorize = function (diff, options = {}, customization) {
 
   return (
     <div className={customization.frameClassName} style={customization.frameStyle}>
-      <pre>{output}</pre>
+      {output}
     </div>
   );
 };
diff --git a/src/JsonDiff/Internal/json-diff.js b/src/JsonDiff/Internal/json-diff.js
index f5037b8..39da9a5 100644
--- a/src/JsonDiff/Internal/json-diff.js
+++ b/src/JsonDiff/Internal/json-diff.js
@@ -1,13 +1,17 @@
 // This is copied from 'json-diff' package ('lib/index.js') with minor
 // modifications.
 
-import { extendedTypeOf } from './utils';
+import { extendedTypeOf, elisionMarker } from './utils';
 import { SequenceMatcher } from '@ewoudenberg/difflib';
 
 export default class JsonDiff {
   constructor(options) {
     options.outputKeys = options.outputKeys || [];
     options.excludeKeys = options.excludeKeys || [];
+
+    // Rendering ”...” elisions in the same way as for arrays
+    options.showElisionsForObjects = options.showElisionsForObjects ?? true;
+
     this.options = options;
   }
 
@@ -55,6 +59,8 @@ export default class JsonDiff {
           equal = false;
         } else if (this.options.full || this.options.outputKeys.includes(key)) {
           result[key] = value1;
+        } else if (this.options.showElisionsForObjects) {
+          result[key] = elisionMarker;
         }
         // console.log(`key ${key} change.score=${change.score} ${change.result}`)
         score += Math.min(20, Math.max(-10, change.score / 5)); // BATMAN!
diff --git a/src/JsonDiff/Internal/utils.ts b/src/JsonDiff/Internal/utils.ts
index 29fba60..a9005b3 100644
--- a/src/JsonDiff/Internal/utils.ts
+++ b/src/JsonDiff/Internal/utils.ts
@@ -27,3 +27,11 @@ export const roundObj = function (data: any, precision: number) {
     return data;
   }
 };
+
+// A hacky marker for “...” elisions for object keys.
+// This feature wasn’t present in the original “json-diff” library.
+// A unique identifier used as a value for the “elisioned” object keys.
+//
+// Read more about “Symbol”s here:
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
+export const elisionMarker = Symbol('json-diff-react--elision-marker');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant