Skip to content

Commit 6b4824e

Browse files
committed
parseValue: fall back to regex parsing when JSON parsing fails
1 parent a8cf904 commit 6b4824e

5 files changed

Lines changed: 70 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 1.49.1 - 2026-03-??
2+
- Fix Filter parseValue method incorrectly handling invalid JSON values
3+
- GH Issue 948: Multi value text choice filters don't work for JSON values, crash the app
4+
15
### 1.49.0 - 2026-03-10
26
- Update TypeScript compiler `lib` option to `"ES2023"'
37
- Add `declarationMap` and `outDir` to TypeScript compiler options

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/api",
3-
"version": "1.49.0",
3+
"version": "1.49.1-fb-gh-948.0",
44
"description": "JavaScript client API for LabKey Server",
55
"scripts": {
66
"build": "npm run build:dist && npm run build:docs",

src/labkey/filter/Types.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,45 @@ describe('Types', () => {
5151
expect(generateFilterTypesSnapshot(Types)).toStrictEqual(typesSnapshot);
5252
});
5353
});
54+
55+
describe('parseValue', () => {
56+
describe('multi value types', () => {
57+
it('should parse JSON formatted values', () => {
58+
const type = Types.IN;
59+
const value = '{json:["value1","value2","value;3"]}';
60+
expect(type.parseValue(value)).toEqual(['value1', 'value2', 'value;3']);
61+
});
62+
63+
it('should split values by the type multi-value separator', () => {
64+
const semicolonType = Types.IN; // Uses ';' as separator
65+
const semicolonValue = 'value1;value2;value3';
66+
expect(semicolonType.parseValue(semicolonValue)).toEqual(['value1', 'value2', 'value3']);
67+
68+
const commaType = Types.BETWEEN; // Uses ',' as separator
69+
const commaValue = 'value1,value2';
70+
expect(commaType.parseValue(commaValue)).toEqual(['value1', 'value2']);
71+
});
72+
73+
it('should split values by newline separator', () => {
74+
const type = Types.IN;
75+
const value = 'value1\nvalue2\nvalue3';
76+
expect(type.parseValue(value)).toEqual(['value1', 'value2', 'value3']);
77+
});
78+
79+
it('should split values by both type separator and newline', () => {
80+
const type = Types.IN;
81+
const value = 'value1;value2\nvalue3';
82+
expect(type.parseValue(value)).toEqual(['value1', 'value2', 'value3']);
83+
});
84+
85+
it('should fall back to regex parsing if JSON is invalid', () => {
86+
const type = Types.IN;
87+
// Invalid JSON: missing closing quote for value2
88+
const singleValue = '{json:["value1","value2]}';
89+
expect(type.parseValue(singleValue)).toEqual([singleValue]);
90+
91+
const multiValue = '{json:aaa;bb}';
92+
expect(type.parseValue(multiValue)).toEqual(['{json:aaa', 'bb}']);
93+
});
94+
})
95+
});

src/labkey/filter/Types.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { isArray, isString } from '../Utils';
16+
import { isString } from '../Utils';
1717

1818
import { FilterValue, multiValueToSingleMap, oppositeMap, singleValueToMultiMap } from './constants';
1919

@@ -557,6 +557,22 @@ export function getFilterTypesForType(jsonType: JsonType, mvEnabled?: boolean):
557557
return types;
558558
}
559559

560+
// Note that while ';' and ',' are both used as primary separators, '\n' is the only secondary separator
561+
const NEW_LINE_SEP = '\n';
562+
563+
export function parseMultiValueFilterString(type: IFilterType, value: string) {
564+
if (value.indexOf('{json:') === 0 && value.indexOf('}') === value.length - 1) {
565+
try {
566+
return JSON.parse(value.substring('{json:'.length, value.length - 1));
567+
} catch {
568+
// GH Issue #948 Purposely do nothing, revert to parsing with regex
569+
}
570+
}
571+
572+
const regexPattern = new RegExp(`[${NEW_LINE_SEP}${type.getMultiValueSeparator()}]`);
573+
return value.split(regexPattern);
574+
}
575+
560576
/**
561577
* Creates a FilterType object and stores it in the global URL Map used by Filter.getFilterTypeForURLSuffix
562578
* @param displayText The text to display in a filter menu
@@ -585,8 +601,6 @@ export function registerFilterType(
585601
const isDataValueRequired = () => dataValueRequired === true;
586602
const isMultiValued = () => multiValueSeparator != null;
587603
const isTableWise = () => tableWise === true;
588-
// Note that while ';' and ',' are both used as primary separators, '\n' is the only secondary separator
589-
const NEW_LINE_SEP = '\n';
590604

591605
const type: IFilterType = {
592606
getDisplaySymbol: () => displaySymbol ?? null,
@@ -606,15 +620,10 @@ export function registerFilterType(
606620
parseValue: value => {
607621
if (type.isMultiValued()) {
608622
if (isString(value)) {
609-
if (value.indexOf('{json:') === 0 && value.indexOf('}') === value.length - 1) {
610-
value = JSON.parse(value.substring('{json:'.length, value.length - 1));
611-
} else {
612-
const regexPattern = new RegExp(`[${NEW_LINE_SEP}${type.getMultiValueSeparator()}]`);
613-
value = value.split(regexPattern);
614-
}
623+
value = parseMultiValueFilterString(type, value);
615624
}
616625

617-
if (!isArray(value))
626+
if (!Array.isArray(value))
618627
throw new Error(
619628
"Filter '" +
620629
type.getDisplayText() +
@@ -625,7 +634,7 @@ export function registerFilterType(
625634
);
626635
}
627636

628-
if (!type.isMultiValued() && isArray(value))
637+
if (!type.isMultiValued() && Array.isArray(value))
629638
throw new Error("Array of values not supported for '" + type.getDisplayText() + "' filter: " + value);
630639

631640
return value;
@@ -636,7 +645,7 @@ export function registerFilterType(
636645
return '';
637646
}
638647

639-
if (type.isMultiValued() && isArray(value)) {
648+
if (type.isMultiValued() && Array.isArray(value)) {
640649
// 35265: Create alternate syntax to handle semicolons
641650
const sep = type.getMultiValueSeparator();
642651
const found = value.some((v: string) => {

0 commit comments

Comments
 (0)