@fincity/kirun-js 2.16.2 → 3.0.0

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.
@@ -2,8 +2,6 @@ import { KIRuntimeException } from '../../../exception/KIRuntimeException';
2
2
  import { isNullValue } from '../../../util/NullCheck';
3
3
  import { duplicate } from '../../../util/duplicate';
4
4
  import { StringFormatter } from '../../../util/string/StringFormatter';
5
- import { Expression } from '../Expression';
6
- import { ExpressionTokenValue } from '../ExpressionTokenValue';
7
5
  import { Operation } from '../Operation';
8
6
  import { TokenValueExtractor } from './TokenValueExtractor';
9
7
 
@@ -16,7 +14,7 @@ export class ObjectValueSetterExtractor extends TokenValueExtractor {
16
14
  this.prefix = prefix;
17
15
  }
18
16
  protected getValueInternal(token: string) {
19
- let parts: string[] = token.split(TokenValueExtractor.REGEX_DOT);
17
+ let parts: string[] = TokenValueExtractor.splitPath(token);
20
18
  return this.retrieveElementFrom(token, parts, 1, this.store);
21
19
  }
22
20
 
@@ -44,38 +42,143 @@ export class ObjectValueSetterExtractor extends TokenValueExtractor {
44
42
  overwrite: boolean,
45
43
  deleteOnNull: boolean,
46
44
  ) {
47
- const exp = new Expression(stringToken);
48
- const tokens = exp.getTokens();
49
- tokens.removeLast();
50
- const ops = exp.getOperations();
51
-
52
- let op = ops.removeLast();
53
- let token = tokens.removeLast();
54
- let mem =
55
- token instanceof ExpressionTokenValue
56
- ? (token as ExpressionTokenValue).getElement()
57
- : token.getExpression();
58
-
45
+ // Use TokenValueExtractor.splitPath to get path segments instead of Expression parsing
46
+ // This is more reliable as it directly handles the path string
47
+ const parts = TokenValueExtractor.splitPath(stringToken);
48
+
49
+ if (parts.length < 2) {
50
+ throw new KIRuntimeException(
51
+ StringFormatter.format('Invalid path: $', stringToken),
52
+ );
53
+ }
54
+
55
+ // Start from index 1 (skip the prefix like 'Store')
59
56
  let el = this.store;
60
-
61
- while (!ops.isEmpty()) {
62
- if (op == Operation.OBJECT_OPERATOR) {
63
- el = this.getDataFromObject(el, mem, ops.peekLast());
57
+
58
+ // Navigate to the parent of the final element
59
+ for (let i = 1; i < parts.length - 1; i++) {
60
+ const part = parts[i];
61
+ const nextPart = parts[i + 1];
62
+
63
+ // Parse bracket segments within this part
64
+ const segments = this.parseBracketSegments(part);
65
+
66
+ for (let j = 0; j < segments.length; j++) {
67
+ const segment = segments[j];
68
+ const isLastSegment = (i === parts.length - 2 && j === segments.length - 1);
69
+ const nextOp = isLastSegment ? this.getOpForSegment(parts[parts.length - 1]) : this.getOpForSegment(nextPart);
70
+
71
+ if (this.isArrayIndex(segment)) {
72
+ el = this.getDataFromArray(el, segment, nextOp);
73
+ } else {
74
+ el = this.getDataFromObject(el, this.stripQuotes(segment), nextOp);
75
+ }
76
+ }
77
+ }
78
+
79
+ // Handle the final part (set the value)
80
+ const finalPart = parts[parts.length - 1];
81
+ const finalSegments = this.parseBracketSegments(finalPart);
82
+
83
+ // Navigate through all but the last segment of the final part
84
+ for (let j = 0; j < finalSegments.length - 1; j++) {
85
+ const segment = finalSegments[j];
86
+ const nextOp = this.isArrayIndex(finalSegments[j + 1]) ? Operation.ARRAY_OPERATOR : Operation.OBJECT_OPERATOR;
87
+
88
+ if (this.isArrayIndex(segment)) {
89
+ el = this.getDataFromArray(el, segment, nextOp);
64
90
  } else {
65
- el = this.getDataFromArray(el, mem, ops.peekLast());
91
+ el = this.getDataFromObject(el, this.stripQuotes(segment), nextOp);
66
92
  }
67
-
68
- op = ops.removeLast();
69
- token = tokens.removeLast();
70
- mem =
71
- token instanceof ExpressionTokenValue
72
- ? (token as ExpressionTokenValue).getElement()
73
- : token.getExpression();
74
93
  }
75
-
76
- if (op == Operation.OBJECT_OPERATOR)
77
- this.putDataInObject(el, mem, value, overwrite, deleteOnNull);
78
- else this.putDataInArray(el, mem, value, overwrite, deleteOnNull);
94
+
95
+ // Set the final value
96
+ const lastSegment = finalSegments[finalSegments.length - 1];
97
+ if (this.isArrayIndex(lastSegment)) {
98
+ this.putDataInArray(el, lastSegment, value, overwrite, deleteOnNull);
99
+ } else {
100
+ this.putDataInObject(el, this.stripQuotes(lastSegment), value, overwrite, deleteOnNull);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Parse a path segment that may contain bracket notation.
106
+ * E.g., "addresses[0]" -> ["addresses", "0"]
107
+ * E.g., 'obj["key"]' -> ["obj", "key"]
108
+ */
109
+ private parseBracketSegments(part: string): string[] {
110
+ const segments: string[] = [];
111
+ let start = 0;
112
+ let i = 0;
113
+
114
+ while (i < part.length) {
115
+ if (part[i] === '[') {
116
+ if (i > start) {
117
+ segments.push(part.substring(start, i));
118
+ }
119
+ // Find matching ]
120
+ let end = i + 1;
121
+ let inQuote = false;
122
+ let quoteChar = '';
123
+ while (end < part.length) {
124
+ if (inQuote) {
125
+ if (part[end] === quoteChar && part[end - 1] !== '\\') {
126
+ inQuote = false;
127
+ }
128
+ } else {
129
+ if (part[end] === '"' || part[end] === "'") {
130
+ inQuote = true;
131
+ quoteChar = part[end];
132
+ } else if (part[end] === ']') {
133
+ break;
134
+ }
135
+ }
136
+ end++;
137
+ }
138
+ // Extract bracket content (without the brackets)
139
+ segments.push(part.substring(i + 1, end));
140
+ start = end + 1;
141
+ i = start;
142
+ } else {
143
+ i++;
144
+ }
145
+ }
146
+
147
+ if (start < part.length) {
148
+ segments.push(part.substring(start));
149
+ }
150
+
151
+ return segments.length > 0 ? segments : [part];
152
+ }
153
+
154
+ /**
155
+ * Check if a segment is an array index (numeric)
156
+ */
157
+ private isArrayIndex(segment: string): boolean {
158
+ // Check if it's a pure number (possibly negative)
159
+ return /^-?\d+$/.test(segment);
160
+ }
161
+
162
+ /**
163
+ * Strip quotes from a segment if present
164
+ */
165
+ private stripQuotes(segment: string): string {
166
+ if ((segment.startsWith('"') && segment.endsWith('"')) ||
167
+ (segment.startsWith("'") && segment.endsWith("'"))) {
168
+ return segment.substring(1, segment.length - 1);
169
+ }
170
+ return segment;
171
+ }
172
+
173
+ /**
174
+ * Determine the operation type for the next segment
175
+ */
176
+ private getOpForSegment(segment: string): Operation {
177
+ // Check if the segment starts with a bracket or is a pure number
178
+ if (this.isArrayIndex(segment) || segment.startsWith('[')) {
179
+ return Operation.ARRAY_OPERATOR;
180
+ }
181
+ return Operation.OBJECT_OPERATOR;
79
182
  }
80
183
 
81
184
  private getDataFromArray(el: any, mem: string, nextOp: Operation): any {
@@ -143,13 +143,23 @@ export abstract class TokenValueExtractor {
143
143
 
144
144
  // Fast path: simple property access on object (most common case)
145
145
  if (typeof element === 'object' && !Array.isArray(element)) {
146
- if (segment in element) {
147
- return element[segment];
148
- }
149
- // Check for 'length' on object
146
+ // For 'length' on objects, check if there's a length property
147
+ // If it's a primitive (number, string, boolean), use it
148
+ // If it's an object/array, use Object.keys length to avoid bugs
150
149
  if (segment === 'length') {
150
+ if ('length' in element) {
151
+ const lengthValue = element['length'];
152
+ // If length property is a primitive, use it; otherwise use Object.keys length
153
+ if (typeof lengthValue === 'object' && lengthValue !== null) {
154
+ return Object.keys(element).length;
155
+ }
156
+ return lengthValue;
157
+ }
151
158
  return Object.keys(element).length;
152
159
  }
160
+ if (segment in element) {
161
+ return element[segment];
162
+ }
153
163
  return element[segment];
154
164
  }
155
165
 
@@ -191,7 +201,10 @@ export abstract class TokenValueExtractor {
191
201
  ): any {
192
202
  if (isNullValue(cElement)) return undefined;
193
203
 
194
- if (cPart === 'length') return this.getLength(token, cElement);
204
+ // Check for 'length' keyword - both unquoted and quoted versions
205
+ // e.g., .length and ["length"] should both return the length
206
+ if (cPart === 'length' || cPart === '"length"' || cPart === "'length'")
207
+ return this.getLength(token, cElement);
195
208
 
196
209
  if (typeof cElement == 'string' || Array.isArray(cElement))
197
210
  return this.handleArrayAccess(token, cPart, cElement);
@@ -204,8 +217,18 @@ export abstract class TokenValueExtractor {
204
217
 
205
218
  if (type === 'string' || Array.isArray(cElement)) return cElement.length;
206
219
  if (type === 'object') {
207
- if ('length' in cElement) return cElement['length'];
208
- else return Object.keys(cElement).length;
220
+ // For objects, check if there's a length property
221
+ // If it's a primitive (number, string, boolean), use it
222
+ // If it's an object/array, use Object.keys length to avoid bugs
223
+ if ('length' in cElement) {
224
+ const lengthValue = cElement['length'];
225
+ // If length property is a primitive, use it; otherwise use Object.keys length
226
+ if (typeof lengthValue === 'object' && lengthValue !== null) {
227
+ return Object.keys(cElement).length;
228
+ }
229
+ return lengthValue;
230
+ }
231
+ return Object.keys(cElement).length;
209
232
  }
210
233
 
211
234
  throw new ExpressionEvaluationException(
@@ -263,7 +286,8 @@ export abstract class TokenValueExtractor {
263
286
  // Handle both single and double quoted keys
264
287
  if (cPart.startsWith('"') || cPart.startsWith("'")) {
265
288
  const quoteChar = cPart[0];
266
- if (!cPart.endsWith(quoteChar) || cPart.length == 1 || cPart.length == 2) {
289
+ // Allow empty string key: "" or ''
290
+ if (!cPart.endsWith(quoteChar) || cPart.length == 1) {
267
291
  throw new ExpressionEvaluationException(
268
292
  token,
269
293
  StringFormatter.format('$ is missing a closing quote or empty key found', token),
@@ -69,7 +69,7 @@ export class LinkedList<T> {
69
69
 
70
70
  let x = this.head;
71
71
  while (index > 0) {
72
- x = this.head!.next;
72
+ x = x!.next;
73
73
  --index;
74
74
  }
75
75