refactor(json): Consolidate JSON utilities and type definitions
Modified files: - client/src/components/DynamicJsonForm.tsx - client/src/components/JsonView.tsx - client/src/components/ToolsTab.tsx - client/src/components/__tests__/DynamicJsonForm.test.tsx - client/src/utils/__tests__/jsonPathUtils.test.ts → client/src/utils/__tests__/jsonUtils.test.ts - client/src/utils/__tests__/schemaUtils.test.ts - client/src/utils/jsonPathUtils.ts → client/src/utils/jsonUtils.ts - client/src/utils/schemaUtils.ts Descriptions: - Move JSON type definitions from DynamicJsonForm to centralized jsonUtils - Consolidate utility functions (getDataType, tryParseJson) into jsonUtils - Rename jsonPathUtils to jsonUtils for better semantic clarity - Add comprehensive test coverage for new utility functions - Update imports across all affected components - Improve type references consistency throughout the codebase
This commit is contained in:
199
client/src/utils/jsonUtils.ts
Normal file
199
client/src/utils/jsonUtils.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
export type JsonValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| JsonValue[]
|
||||
| { [key: string]: JsonValue };
|
||||
|
||||
export type JsonSchemaType = {
|
||||
type:
|
||||
| "string"
|
||||
| "number"
|
||||
| "integer"
|
||||
| "boolean"
|
||||
| "array"
|
||||
| "object"
|
||||
| "null";
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
default?: JsonValue;
|
||||
properties?: Record<string, JsonSchemaType>;
|
||||
items?: JsonSchemaType;
|
||||
};
|
||||
|
||||
export type JsonObject = { [key: string]: JsonValue };
|
||||
|
||||
const typeofVariable = typeof "random variable";
|
||||
export function getDataType(
|
||||
value: JsonValue,
|
||||
): typeof typeofVariable | "array" | "null" {
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (value === null) return "null";
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
export function tryParseJson(str: string): {
|
||||
success: boolean;
|
||||
data: JsonValue;
|
||||
} {
|
||||
const trimmed = str.trim();
|
||||
if (
|
||||
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
|
||||
!(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
||||
) {
|
||||
return { success: false, data: str };
|
||||
}
|
||||
try {
|
||||
return { success: true, data: JSON.parse(str) };
|
||||
} catch {
|
||||
return { success: false, data: str };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a value at a specific path in a nested JSON structure
|
||||
* @param obj The original JSON value
|
||||
* @param path Array of keys/indices representing the path to the value
|
||||
* @param value The new value to set
|
||||
* @returns A new JSON value with the updated path
|
||||
*/
|
||||
export function updateValueAtPath(
|
||||
obj: JsonValue,
|
||||
path: string[],
|
||||
value: JsonValue,
|
||||
): JsonValue {
|
||||
if (path.length === 0) return value;
|
||||
|
||||
if (obj === null || obj === undefined) {
|
||||
obj = !isNaN(Number(path[0])) ? [] : {};
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return updateArray(obj, path, value);
|
||||
} else if (typeof obj === "object" && obj !== null) {
|
||||
return updateObject(obj as JsonObject, path, value);
|
||||
} else {
|
||||
console.error(
|
||||
`Cannot update path ${path.join(".")} in non-object/array value:`,
|
||||
obj,
|
||||
);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an array at a specific path
|
||||
*/
|
||||
function updateArray(
|
||||
array: JsonValue[],
|
||||
path: string[],
|
||||
value: JsonValue,
|
||||
): JsonValue[] {
|
||||
const [index, ...restPath] = path;
|
||||
const arrayIndex = Number(index);
|
||||
|
||||
if (isNaN(arrayIndex)) {
|
||||
console.error(`Invalid array index: ${index}`);
|
||||
return array;
|
||||
}
|
||||
|
||||
if (arrayIndex < 0) {
|
||||
console.error(`Array index out of bounds: ${arrayIndex} < 0`);
|
||||
return array;
|
||||
}
|
||||
|
||||
let newArray: JsonValue[] = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
newArray[i] = i in array ? array[i] : null;
|
||||
}
|
||||
|
||||
if (arrayIndex >= newArray.length) {
|
||||
const extendedArray: JsonValue[] = new Array(arrayIndex).fill(null);
|
||||
// Copy over the existing elements (now guaranteed to be dense)
|
||||
for (let i = 0; i < newArray.length; i++) {
|
||||
extendedArray[i] = newArray[i];
|
||||
}
|
||||
newArray = extendedArray;
|
||||
}
|
||||
|
||||
if (restPath.length === 0) {
|
||||
newArray[arrayIndex] = value;
|
||||
} else {
|
||||
newArray[arrayIndex] = updateValueAtPath(
|
||||
newArray[arrayIndex],
|
||||
restPath,
|
||||
value,
|
||||
);
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an object at a specific path
|
||||
*/
|
||||
function updateObject(
|
||||
obj: JsonObject,
|
||||
path: string[],
|
||||
value: JsonValue,
|
||||
): JsonObject {
|
||||
const [key, ...restPath] = path;
|
||||
|
||||
// Validate object key
|
||||
if (typeof key !== "string") {
|
||||
console.error(`Invalid object key: ${key}`);
|
||||
return obj;
|
||||
}
|
||||
|
||||
const newObj = { ...obj };
|
||||
|
||||
if (restPath.length === 0) {
|
||||
newObj[key] = value;
|
||||
} else {
|
||||
// Ensure key exists
|
||||
if (!(key in newObj)) {
|
||||
newObj[key] = {};
|
||||
}
|
||||
newObj[key] = updateValueAtPath(newObj[key], restPath, value);
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value at a specific path in a nested JSON structure
|
||||
* @param obj The JSON value to traverse
|
||||
* @param path Array of keys/indices representing the path to the value
|
||||
* @param defaultValue Value to return if path doesn't exist
|
||||
* @returns The value at the path, or defaultValue if not found
|
||||
*/
|
||||
export function getValueAtPath(
|
||||
obj: JsonValue,
|
||||
path: string[],
|
||||
defaultValue: JsonValue = null,
|
||||
): JsonValue {
|
||||
if (path.length === 0) return obj;
|
||||
|
||||
const [first, ...rest] = path;
|
||||
|
||||
if (obj === null || obj === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
const index = Number(first);
|
||||
if (isNaN(index) || index < 0 || index >= obj.length) {
|
||||
return defaultValue;
|
||||
}
|
||||
return getValueAtPath(obj[index], rest, defaultValue);
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
if (!(first in obj)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return getValueAtPath((obj as JsonObject)[first], rest, defaultValue);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
Reference in New Issue
Block a user