@twin.org/data-processing-extractors 0.0.2-next.2 → 0.0.2-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ class JsonPathExtractorConnector {
16
16
  /**
17
17
  * Runtime name for the class.
18
18
  */
19
- CLASS_NAME = "JsonPathExtractorConnector";
19
+ static CLASS_NAME = "JsonPathExtractorConnector";
20
20
  /**
21
21
  * Extracts data from the provided input.
22
22
  * @param data The object to extract from.
@@ -24,8 +24,8 @@ class JsonPathExtractorConnector {
24
24
  * @returns The extracted data.
25
25
  */
26
26
  async extract(data, rules) {
27
- core.Guards.object(this.CLASS_NAME, "data", data);
28
- core.Guards.array(this.CLASS_NAME, "rules", rules);
27
+ core.Guards.object(JsonPathExtractorConnector.CLASS_NAME, "data", data);
28
+ core.Guards.array(JsonPathExtractorConnector.CLASS_NAME, "rules", rules);
29
29
  const outputObject = {};
30
30
  for (const extractRule of rules) {
31
31
  this.extractValue(data, extractRule, outputObject);
@@ -42,14 +42,44 @@ class JsonPathExtractorConnector {
42
42
  extractValue(jsonObject, rule, outputObject) {
43
43
  try {
44
44
  const jsonNodes = jsonP3.query(rule.source, jsonObject);
45
+ const ruleParts = rule.source.split(".");
46
+ // Group values by their target path to handle multiple results correctly
47
+ const pathToValues = {};
45
48
  for (const jsonNode of jsonNodes) {
46
49
  const retainPathDepth = rule.retainPathDepth ?? 0;
47
50
  const fullTargetPath = `${rule.target}${retainPathDepth > 0 ? `.${jsonNode.location.slice(-retainPathDepth).join(".")}` : ""}`;
48
- core.ObjectHelper.propertySet(outputObject, fullTargetPath, core.Coerce.byType(jsonNode.value, rule.coerce));
51
+ const coercedValue = core.Coerce.byType(jsonNode.value, rule.coerce);
52
+ const nodeLocation = jsonNode.location;
53
+ // If the last part of the location before the target property is an integer
54
+ // and the corresponding rule part ends with [*], we are dealing with an array
55
+ const isArray = nodeLocation.length > 1 &&
56
+ ruleParts.length > 1 &&
57
+ core.Is.integer(nodeLocation[nodeLocation.length - 2]) &&
58
+ ruleParts[nodeLocation.length - 2].endsWith("[*]");
59
+ if (isArray) {
60
+ // If the value is set and not currently an array, convert it to one
61
+ if (!core.Is.empty(pathToValues[fullTargetPath]) && !core.Is.array(pathToValues[fullTargetPath])) {
62
+ pathToValues[fullTargetPath] = [pathToValues[fullTargetPath]];
63
+ }
64
+ // Make sure the array exists
65
+ pathToValues[fullTargetPath] ??= [];
66
+ // And add the new value
67
+ if (core.Is.array(pathToValues[fullTargetPath])) {
68
+ pathToValues[fullTargetPath].push(coercedValue);
69
+ }
70
+ }
71
+ else {
72
+ // For single values, just set the value directly
73
+ pathToValues[fullTargetPath] = coercedValue;
74
+ }
75
+ }
76
+ // Set the values in the output object
77
+ for (const [path, values] of Object.entries(pathToValues)) {
78
+ core.ObjectHelper.propertySet(outputObject, path, values);
49
79
  }
50
80
  }
51
81
  catch (err) {
52
- throw new core.GeneralError(this.CLASS_NAME, "invalidRule", {
82
+ throw new core.GeneralError(JsonPathExtractorConnector.CLASS_NAME, "invalidRule", {
53
83
  rule: rule.source,
54
84
  reason: core.BaseError.fromError(err).message
55
85
  });
@@ -1,4 +1,4 @@
1
- import { Guards, ObjectHelper, Coerce, GeneralError, BaseError } from '@twin.org/core';
1
+ import { Guards, Coerce, Is, ObjectHelper, GeneralError, BaseError } from '@twin.org/core';
2
2
  import { query } from 'json-p3';
3
3
 
4
4
  // Copyright 2024 IOTA Stiftung.
@@ -14,7 +14,7 @@ class JsonPathExtractorConnector {
14
14
  /**
15
15
  * Runtime name for the class.
16
16
  */
17
- CLASS_NAME = "JsonPathExtractorConnector";
17
+ static CLASS_NAME = "JsonPathExtractorConnector";
18
18
  /**
19
19
  * Extracts data from the provided input.
20
20
  * @param data The object to extract from.
@@ -22,8 +22,8 @@ class JsonPathExtractorConnector {
22
22
  * @returns The extracted data.
23
23
  */
24
24
  async extract(data, rules) {
25
- Guards.object(this.CLASS_NAME, "data", data);
26
- Guards.array(this.CLASS_NAME, "rules", rules);
25
+ Guards.object(JsonPathExtractorConnector.CLASS_NAME, "data", data);
26
+ Guards.array(JsonPathExtractorConnector.CLASS_NAME, "rules", rules);
27
27
  const outputObject = {};
28
28
  for (const extractRule of rules) {
29
29
  this.extractValue(data, extractRule, outputObject);
@@ -40,14 +40,44 @@ class JsonPathExtractorConnector {
40
40
  extractValue(jsonObject, rule, outputObject) {
41
41
  try {
42
42
  const jsonNodes = query(rule.source, jsonObject);
43
+ const ruleParts = rule.source.split(".");
44
+ // Group values by their target path to handle multiple results correctly
45
+ const pathToValues = {};
43
46
  for (const jsonNode of jsonNodes) {
44
47
  const retainPathDepth = rule.retainPathDepth ?? 0;
45
48
  const fullTargetPath = `${rule.target}${retainPathDepth > 0 ? `.${jsonNode.location.slice(-retainPathDepth).join(".")}` : ""}`;
46
- ObjectHelper.propertySet(outputObject, fullTargetPath, Coerce.byType(jsonNode.value, rule.coerce));
49
+ const coercedValue = Coerce.byType(jsonNode.value, rule.coerce);
50
+ const nodeLocation = jsonNode.location;
51
+ // If the last part of the location before the target property is an integer
52
+ // and the corresponding rule part ends with [*], we are dealing with an array
53
+ const isArray = nodeLocation.length > 1 &&
54
+ ruleParts.length > 1 &&
55
+ Is.integer(nodeLocation[nodeLocation.length - 2]) &&
56
+ ruleParts[nodeLocation.length - 2].endsWith("[*]");
57
+ if (isArray) {
58
+ // If the value is set and not currently an array, convert it to one
59
+ if (!Is.empty(pathToValues[fullTargetPath]) && !Is.array(pathToValues[fullTargetPath])) {
60
+ pathToValues[fullTargetPath] = [pathToValues[fullTargetPath]];
61
+ }
62
+ // Make sure the array exists
63
+ pathToValues[fullTargetPath] ??= [];
64
+ // And add the new value
65
+ if (Is.array(pathToValues[fullTargetPath])) {
66
+ pathToValues[fullTargetPath].push(coercedValue);
67
+ }
68
+ }
69
+ else {
70
+ // For single values, just set the value directly
71
+ pathToValues[fullTargetPath] = coercedValue;
72
+ }
73
+ }
74
+ // Set the values in the output object
75
+ for (const [path, values] of Object.entries(pathToValues)) {
76
+ ObjectHelper.propertySet(outputObject, path, values);
47
77
  }
48
78
  }
49
79
  catch (err) {
50
- throw new GeneralError(this.CLASS_NAME, "invalidRule", {
80
+ throw new GeneralError(JsonPathExtractorConnector.CLASS_NAME, "invalidRule", {
51
81
  rule: rule.source,
52
82
  reason: BaseError.fromError(err).message
53
83
  });
@@ -10,7 +10,7 @@ export declare class JsonPathExtractorConnector implements IDataExtractorConnect
10
10
  /**
11
11
  * Runtime name for the class.
12
12
  */
13
- readonly CLASS_NAME: string;
13
+ static readonly CLASS_NAME: string;
14
14
  /**
15
15
  * Extracts data from the provided input.
16
16
  * @param data The object to extract from.
package/docs/changelog.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @twin.org/data-processing-extractors - Changelog
2
2
 
3
+ ## [0.0.2-next.4](https://github.com/twinfoundation/data-processing/compare/data-processing-extractors-v0.0.2-next.3...data-processing-extractors-v0.0.2-next.4) (2025-10-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * supporting array usage ([#20](https://github.com/twinfoundation/data-processing/issues/20)) ([d2c68cd](https://github.com/twinfoundation/data-processing/commit/d2c68cd3983622586cc073377545d6416b87a827))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/data-processing-models bumped from 0.0.2-next.3 to 0.0.2-next.4
16
+
17
+ ## [0.0.2-next.3](https://github.com/twinfoundation/data-processing/compare/data-processing-extractors-v0.0.2-next.2...data-processing-extractors-v0.0.2-next.3) (2025-10-09)
18
+
19
+
20
+ ### Features
21
+
22
+ * add validate-locales ([9af0874](https://github.com/twinfoundation/data-processing/commit/9af0874cd3f44a4cfca514dde8380f002e62337e))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @twin.org/data-processing-models bumped from 0.0.2-next.2 to 0.0.2-next.3
30
+
3
31
  ## [0.0.2-next.2](https://github.com/twinfoundation/data-processing/compare/data-processing-extractors-v0.0.2-next.1...data-processing-extractors-v0.0.2-next.2) (2025-08-29)
4
32
 
5
33
 
@@ -28,14 +28,10 @@ The namespace supported by the data extractor connector.
28
28
 
29
29
  ### CLASS\_NAME
30
30
 
31
- > `readonly` **CLASS\_NAME**: `string`
31
+ > `readonly` `static` **CLASS\_NAME**: `string`
32
32
 
33
33
  Runtime name for the class.
34
34
 
35
- #### Implementation of
36
-
37
- `IDataExtractorConnector.CLASS_NAME`
38
-
39
35
  ## Methods
40
36
 
41
37
  ### extract()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/data-processing-extractors",
3
- "version": "0.0.2-next.2",
3
+ "version": "0.0.2-next.4",
4
4
  "description": "Data extractor connector implementations",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@twin.org/core": "next",
18
- "@twin.org/data-processing-models": "0.0.2-next.2",
18
+ "@twin.org/data-processing-models": "0.0.2-next.4",
19
19
  "@twin.org/nameof": "next",
20
20
  "@twin.org/web": "next",
21
21
  "json-p3": "2.2.2"
@@ -37,5 +37,21 @@
37
37
  "dist/types",
38
38
  "locales",
39
39
  "docs"
40
- ]
40
+ ],
41
+ "keywords": [
42
+ "twin",
43
+ "trade",
44
+ "iota",
45
+ "framework",
46
+ "blockchain",
47
+ "data-processing",
48
+ "data",
49
+ "processing",
50
+ "transformation",
51
+ "conversion"
52
+ ],
53
+ "bugs": {
54
+ "url": "git+https://github.com/twinfoundation/data-processing/issues"
55
+ },
56
+ "homepage": "https://twindev.org"
41
57
  }