]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
parsing: support dot(s) in object keys (#10517)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Tue, 26 Jul 2022 12:30:52 +0000 (15:30 +0300)
committerGitHub <noreply@github.com>
Tue, 26 Jul 2022 12:30:52 +0000 (08:30 -0400)
docs/general/data-structures.md
src/helpers/helpers.core.js
test/specs/helpers.core.tests.js

index 37d3e63df3e48f3d303f6c62eb2e3ec1cda9853a..117a7e38834692916eb938fdf201bab59fc9409b 100644 (file)
@@ -85,11 +85,27 @@ options: {
 }
 ```
 
+If the key contains a dot, it needs to be escaped with a double slash:
+
+```javascript
+type: 'line',
+data: {
+    datasets: [{
+        data: [{ 'data.key': 'one', 'data.value': 20 }, { 'data.key': 'two', 'data.value': 30 }]
+    }]
+},
+options: {
+    parsing: {
+      xAxisKey: 'data\\.key',
+      yAxisKey: 'data\\.value'
+    }
+}
+```
+
 :::warning
 When using object notation in a radar chart you still need a labels array with labels for the chart to show correctly.
 :::
 
-
 ## Object
 
 ```javascript
index 00c143aa32575ca09766a543ee7d6f0bb134a8bb..ce15bc63108ac43ea0a577b0600d819b88b78e04 100644 (file)
@@ -293,25 +293,52 @@ export function _deprecated(scope, value, previous, current) {
   }
 }
 
-const emptyString = '';
-const dot = '.';
-function indexOfDotOrLength(key, start) {
-  const idx = key.indexOf(dot, start);
-  return idx === -1 ? key.length : idx;
-}
+// resolveObjectKey resolver cache
+const keyResolvers = {
+  // Chart.helpers.core resolveObjectKey should resolve empty key to root object
+  '': v => v,
+  // default resolvers
+  x: o => o.x,
+  y: o => o.y
+};
 
 export function resolveObjectKey(obj, key) {
-  if (key === emptyString) {
+  const resolver = keyResolvers[key] || (keyResolvers[key] = _getKeyResolver(key));
+  return resolver(obj);
+}
+
+function _getKeyResolver(key) {
+  const keys = _splitKey(key);
+  return obj => {
+    for (const k of keys) {
+      if (k === '') {
+        // For backward compatibility:
+        // Chart.helpers.core resolveObjectKey should break at empty key
+        break;
+      }
+      obj = obj && obj[k];
+    }
     return obj;
+  };
+}
+
+/**
+ * @private
+ */
+export function _splitKey(key) {
+  const parts = key.split('.');
+  const keys = [];
+  let tmp = '';
+  for (const part of parts) {
+    tmp += part;
+    if (tmp.endsWith('\\')) {
+      tmp = tmp.slice(0, -1) + '.';
+    } else {
+      keys.push(tmp);
+      tmp = '';
+    }
   }
-  let pos = 0;
-  let idx = indexOfDotOrLength(key, pos);
-  while (obj && idx > pos) {
-    obj = obj[key.slice(pos, idx)];
-    pos = idx + 1;
-    idx = indexOfDotOrLength(key, pos);
-  }
-  return obj;
+  return keys;
 }
 
 /**
index e3d1400b17e8bb6e81ba3832804c346dfe6d26a0..c5c51434c882cb883951fa822937109792c5251f 100644 (file)
@@ -456,6 +456,44 @@ describe('Chart.helpers.core', function() {
       expect(() => helpers.resolveObjectKey({}, true)).toThrow();
       expect(() => helpers.resolveObjectKey({}, 1)).toThrow();
     });
+    it('should allow escaping dot symbol', function() {
+      expect(helpers.resolveObjectKey({'test.dot': 10}, 'test\\.dot')).toEqual(10);
+      expect(helpers.resolveObjectKey({test: {dot: 10}}, 'test\\.dot')).toEqual(undefined);
+    });
+    it('should allow nested keys with a dot', function() {
+      expect(helpers.resolveObjectKey({
+        a: {
+          'bb.ccc': 'works',
+          bb: {
+            ccc: 'fails'
+          }
+        }
+      }, 'a.bb\\.ccc')).toEqual('works');
+    });
+
+  });
+
+  describe('_splitKey', function() {
+    it('should return array with one entry for string without a dot', function() {
+      expect(helpers._splitKey('')).toEqual(['']);
+      expect(helpers._splitKey('test')).toEqual(['test']);
+      const asciiWithoutDot = ' !"#$%&\'()*+,-/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
+      expect(helpers._splitKey(asciiWithoutDot)).toEqual([asciiWithoutDot]);
+    });
+
+    it('should split on dot', function() {
+      expect(helpers._splitKey('test1.test2')).toEqual(['test1', 'test2']);
+      expect(helpers._splitKey('a.b.c')).toEqual(['a', 'b', 'c']);
+      expect(helpers._splitKey('a.b.')).toEqual(['a', 'b', '']);
+      expect(helpers._splitKey('a..c')).toEqual(['a', '', 'c']);
+    });
+
+    it('should preserve escaped dot', function() {
+      expect(helpers._splitKey('test1\\.test2')).toEqual(['test1.test2']);
+      expect(helpers._splitKey('a\\.b.c')).toEqual(['a.b', 'c']);
+      expect(helpers._splitKey('a.b\\.c')).toEqual(['a', 'b.c']);
+      expect(helpers._splitKey('a.\\.c')).toEqual(['a', '.c']);
+    });
   });
 
   describe('setsEqual', function() {