Skip to content

Commit 5204580

Browse files
author
KIvanow
committed
RI-6231 - updated approach based on feedback from the PR
1 parent c4b3332 commit 5204580

File tree

6 files changed

+161
-249
lines changed

6 files changed

+161
-249
lines changed

redisinsight/ui/src/pages/browser/modules/key-details/components/rejson-details/rejson-dynamic-types/RejsonDynamicTypes.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import RejsonDynamicTypes from './RejsonDynamicTypes'
66

77
const mockedProps = mock<DynamicTypesProps>()
88

9-
const mockedDownloadedSimpleArray = [1, 2, 3]
9+
const mockedDownloadedSimpleArray = "[1, 2, 3]"
1010

1111
describe('RejsonDynamicTypes Component', () => {
1212
it('renders correctly simple downloaded JSON', () => {

redisinsight/ui/src/pages/browser/modules/key-details/components/rejson-details/rejson-dynamic-types/RejsonDynamicTypes.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react'
33
import { isNull, isObject } from 'lodash'
44
import { MAX_LEFT_PADDING_NESTING } from '../constants'
55
import { DynamicTypesProps, ObjectTypes } from '../interfaces'
6-
import { generatePath, isScalar } from '../utils'
6+
import { generatePath, isScalar, parseJsonData } from '../utils'
77

88
import RejsonScalar from '../rejson-scalar'
99
import RejsonObject from '../rejson-object'
@@ -102,7 +102,7 @@ const RejsonDynamicTypes = (props: DynamicTypesProps) => {
102102
return Object.entries(data).map(([key, value]) => renderArrayItem(key, value))
103103
}
104104

105-
return renderResult(data)
105+
return renderResult(parseJsonData(data))
106106
}
107107

108108
export default RejsonDynamicTypes

redisinsight/ui/src/pages/browser/modules/key-details/components/rejson-details/utils/utils.spec.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { generatePath, getBrackets, isRealArray, isRealObject, isScalar, isValidKey, wrapPath } from './utils'
1+
import { generatePath, getBrackets, isRealArray, isRealObject, isScalar, isValidKey, parseJsonData, parseValue, wrapPath } from './utils'
22
import { ObjectTypes } from '../interfaces'
33

44
describe('JSONUtils', () => {
@@ -79,4 +79,88 @@ describe('JSONUtils', () => {
7979
expect(isValidKey('"')).toBeFalsy()
8080
})
8181
})
82+
83+
describe('JSON Parsing Utils', () => {
84+
const bigintAsString = '1188950299261208742'
85+
86+
describe('parseValue', () => {
87+
it('should handle non-string values', () => {
88+
expect(parseValue(123)).toBe(123)
89+
expect(parseValue(null)).toBe(null)
90+
expect(parseValue(undefined)).toBe(undefined)
91+
})
92+
93+
it('should parse typed integer values', () => {
94+
const result = parseValue(bigintAsString, 'integer')
95+
expect(typeof result).toBe('bigint')
96+
expect(result.toString()).toBe(bigintAsString)
97+
})
98+
99+
it('should parse regular numbers as numbers, not bigints', () => {
100+
const result = parseValue('42', 'integer')
101+
expect(typeof result).toBe('number')
102+
expect(result).toBe(42)
103+
})
104+
105+
it('should handle string type with quotes', () => {
106+
expect(parseValue('"test"', 'string')).toBe('test')
107+
expect(parseValue('test', 'string')).toBe('test')
108+
})
109+
110+
it('should parse boolean values', () => {
111+
expect(parseValue('true', 'boolean')).toBe(true)
112+
expect(parseValue('false', 'boolean')).toBe(false)
113+
})
114+
115+
it('should parse null values', () => {
116+
expect(parseValue('null', 'null')).toBe(null)
117+
})
118+
119+
it('should parse JSON objects without type', () => {
120+
const input = `{"value": ${bigintAsString}, "text": "test"}`
121+
const result = parseValue(input)
122+
expect(typeof result.value).toBe('bigint')
123+
expect(result.value.toString()).toBe(bigintAsString)
124+
expect(result.text).toBe('test')
125+
})
126+
127+
it('should parse JSON arrays without type', () => {
128+
const input = `[${bigintAsString}, "test"]`
129+
const result = parseValue(input)
130+
expect(typeof result[0]).toBe('bigint')
131+
expect(result[0].toString()).toBe(bigintAsString)
132+
expect(result[1]).toBe('test')
133+
})
134+
})
135+
136+
describe('parseJsonData', () => {
137+
it('should handle null or undefined data', () => {
138+
expect(parseJsonData(null)).toBe(null)
139+
expect(parseJsonData(undefined)).toBe(undefined)
140+
})
141+
142+
it('should parse array of typed values', () => {
143+
const input = [
144+
{ type: 'string', value: '"John"' },
145+
{ type: 'integer', value: bigintAsString }
146+
]
147+
const result = parseJsonData(input)
148+
149+
expect(result[0].value).toBe('John')
150+
expect(typeof result[1].value).toBe('bigint')
151+
expect(result[1].value.toString()).toBe(bigintAsString)
152+
})
153+
154+
it('should preserve non-typed array items', () => {
155+
const input = [
156+
{ value: '"John"' },
157+
{ someOtherProp: 'test' }
158+
]
159+
const result = parseJsonData(input)
160+
161+
expect(result[0].value).toBe('"John"')
162+
expect(result[1].someOtherProp).toBe('test')
163+
})
164+
})
165+
})
82166
})

redisinsight/ui/src/pages/browser/modules/key-details/components/rejson-details/utils/utils.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { isArray } from 'lodash'
22
import { JSONScalarValue, ObjectTypes } from '../interfaces'
33
import styles from '../styles.module.scss'
4+
import JSONBigInt from 'json-bigint'
45

56
enum ClassNames {
67
string = 'jsonString',
@@ -61,3 +62,72 @@ export const getBrackets = (type: string, position: 'start' | 'end' = 'start') =
6162
}
6263

6364
export const isValidKey = (key: string): boolean => /^"([^"\\]|\\.)*"$/.test(key)
65+
66+
const JSONParser = JSONBigInt({
67+
useNativeBigInt: true,
68+
strict: false
69+
})
70+
71+
export const parseValue = (value: any, type?: string): any => {
72+
try {
73+
if (typeof value !== 'string' || !value) {
74+
return value
75+
}
76+
77+
if (type) {
78+
switch (type) {
79+
case 'integer': {
80+
const num = BigInt(value)
81+
return num > Number.MAX_SAFE_INTEGER ? num : Number(value)
82+
}
83+
case 'number':
84+
return Number(value)
85+
case 'boolean':
86+
return value === 'true'
87+
case 'null':
88+
return null
89+
case 'string':
90+
if (value.startsWith('"') && value.endsWith('"')) {
91+
value = value.slice(1, -1)
92+
}
93+
return value
94+
default:
95+
return value
96+
}
97+
}
98+
99+
const parsed = JSONParser.parse(value)
100+
101+
if (typeof parsed === 'object' && parsed !== null) {
102+
if (Array.isArray(parsed)) {
103+
return parsed.map(val => parseValue(val))
104+
}
105+
const result: { [key: string]: any } = {}
106+
Object.entries(parsed).forEach(([key, val]) => {
107+
result[key] = parseValue(val)
108+
})
109+
return result
110+
}
111+
return parsed
112+
} catch (e) {
113+
return value
114+
}
115+
}
116+
117+
export const parseJsonData = (data: any) => {
118+
if (!data) {
119+
return data
120+
}
121+
try {
122+
if (data && Array.isArray(data)) {
123+
return data.map((item: { type?: string; value?: any }) => ({
124+
...item,
125+
value: item.type && item.value ? parseValue(item.value, item.type) : item.value
126+
}))
127+
}
128+
129+
return parseValue(data)
130+
} catch (e) {
131+
return data
132+
}
133+
}

redisinsight/ui/src/slices/browser/rejson.ts

Lines changed: 2 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -27,97 +27,6 @@ import { AppDispatch, RootState } from '../store'
2727

2828
const JSON_LENGTH_TO_FORCE_RETRIEVE = 200
2929

30-
// Initialize parser with strict mode off to allow parsing of primitive values
31-
const JSONParser = JSONBigInt({
32-
useNativeBigInt: true,
33-
strict: false // This allows parsing of primitive values
34-
})
35-
36-
const parseValue = (value: any, type?: string): any => {
37-
try {
38-
// Handle non-strings or empty values
39-
if (typeof value !== 'string' || !value) {
40-
return value;
41-
}
42-
43-
// If type is provided, handle typed values first
44-
if (type) {
45-
switch (type) {
46-
case 'integer': {
47-
const num = BigInt(value);
48-
return num > Number.MAX_SAFE_INTEGER ? num : Number(value);
49-
}
50-
case 'number':
51-
return Number(value);
52-
case 'boolean':
53-
return value === 'true';
54-
case 'null':
55-
return null;
56-
case 'string':
57-
if (value.startsWith('"') && value.endsWith('"')) {
58-
value = value.slice(1, -1);
59-
}
60-
return value;
61-
default:
62-
return value;
63-
}
64-
}
65-
66-
const parsed = JSONParser.parse(value);
67-
68-
if (typeof parsed === 'object' && parsed !== null) {
69-
if (Array.isArray(parsed)) {
70-
return parsed.map(val => parseValue(val));
71-
}
72-
const result: { [key: string]: any } = {};
73-
Object.entries(parsed).forEach(([key, val]) => {
74-
result[key] = parseValue(val);
75-
});
76-
return result;
77-
}
78-
return parsed;
79-
} catch (e) {
80-
// If JSON parsing fails, return the processed string
81-
return value;
82-
}
83-
};
84-
85-
const parseJsonData = (data: any) => {
86-
try {
87-
// Handle array of objects with metadata (from BE)
88-
if (data?.data && Array.isArray(data.data)) {
89-
return {
90-
...data,
91-
data: data.data.map((item: { type?: string; value?: any }) => ({
92-
...item,
93-
value: item.type && item.value ? parseValue(item.value, item.type) : item.value
94-
}))
95-
};
96-
}
97-
98-
// Handle regular JSON data
99-
return {
100-
...data,
101-
data: parseValue(data.data)
102-
};
103-
} catch (e) {
104-
console.error('Error parsing JSON data:', e);
105-
return data;
106-
}
107-
};
108-
109-
const parseSimpleJsonData = (data: any) => {
110-
try {
111-
return {
112-
...data,
113-
data: JSONParser.parse(data.data)
114-
};
115-
} catch (e) {
116-
console.error('Error parsing JSON data:', e);
117-
return data;
118-
}
119-
}
120-
12130
export const initialState: InitialStateRejson = {
12231
loading: false,
12332
error: null,
@@ -250,7 +159,7 @@ export function fetchReJSON(
250159

251160
sourceRejson = null
252161
if (isStatusSuccessful(status)) {
253-
dispatch(loadRejsonBranchSuccess(parseSimpleJsonData(data)))
162+
dispatch(loadRejsonBranchSuccess(data))
254163
}
255164
} catch (error) {
256165
if (!axios.isCancel(error)) {
@@ -444,7 +353,7 @@ export function fetchVisualisationResults(path = '.', forceRetrieve = false) {
444353
)
445354

446355
if (isStatusSuccessful(status)) {
447-
return parseJsonData(data)
356+
return data
448357
}
449358
throw new Error(data.toString())
450359
} catch (_err) {

0 commit comments

Comments
 (0)