JSON Processing Utilities
Overview
The JSON processing utilities provide essential functions for parsing, filtering, sorting, and manipulating JSON data within the application. These functions handle the core data operations that power the visualizer's search, display, and navigation features.
Key Extraction (extractKeys)
export const extractKeys = (data: JSONObject[]): string[] => {
  const keys = new Set<string>();
  
  const processObject = (obj: JSONObject, prefix = '') => {
    for (const [key, value] of Object.entries(obj)) {
      // Skip internal properties
      if (key.startsWith('__')) continue;
      
      const currentKey = prefix ? `${prefix}.${key}` : key;
      keys.add(currentKey);
      
      // Process nested objects
      if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
        processObject(value as JSONObject, currentKey);
      }
    }
  };
  
  data.forEach(obj => processObject(obj));
  return Array.from(keys);
};
Purpose: Extracts all possible field names from a dataset, including nested object properties.
How it works:
- Uses a Set to automatically deduplicate field names
 - Recursive processing - The 
processObjectfunction calls itself for nested objects - Dot notation - Nested fields are represented as 
parent.child.grandchild - Skips internal fields - Properties starting with 
__(like__id,__flags) are ignored - Type checking - Only processes actual objects, not arrays or primitive values
 
Example:
const data = [
  { name: "John", address: { street: "123 Main St", city: "Boston" } },
  { name: "Jane", phone: "555-1234" }
];
extractKeys(data); 
// Returns: ["name", "address", "address.street", "address.city", "phone"]
Value Formatting (formatValue)
export const formatValue = (value: JSONValue): string => {
  if (value === null) return 'null';
  if (value === undefined) return 'undefined';
  if (typeof value === 'object') return JSON.stringify(value);
  return String(value);
};
Purpose: Converts any JSON value to a string for display purposes.
Handling different types:
- null/undefined - Returns literal strings "null" and "undefined"
 - Objects/Arrays - Converts to JSON string representation
 - Primitives - Converts to string using 
String()constructor 
JSON Parsing (parseJson)
export const parseJson = (jsonString: string): JSONObject[] | null => {
  try {
    const parsed = JSON.parse(jsonString);
    if (Array.isArray(parsed)) {
      return parsed;
    } else if (typeof parsed === 'object' && parsed !== null) {
      return [parsed];
    }
    return null;
  } catch (error) {
    console.error('Failed to parse JSON:', error);
    return null;
  }
};
Purpose: Safely parses JSON strings and normalizes the result to an array format.
Normalization logic:
- Array input - Returns as-is
 - Object input - Wraps in an array 
[object] - Other types - Returns 
null(invalid) - Parse errors - Returns 
nulland logs error 
This ensures the application always works with arrays of objects, regardless of input format.
Path-based Value Access (getValueByPath)
export const getValueByPath = (obj: JSONObject, path: string): JSONValue | undefined => {
  const keys = path.split('.');
  return keys.reduce<unknown>((o, key) => {
    if (o && typeof o === 'object' && key in o) {
      return (o as Record<string, unknown>)[key];
    }
    return undefined;
  }, obj);
};
Purpose: Retrieves values from nested objects using dot notation paths.
How it works:
- Splits path - 
"address.street"becomes["address", "street"] - Reduces through keys - Traverses the object one key at a time
 - Type safety - Checks that each step is an object before accessing properties
 - Graceful failure - Returns 
undefinedif any part of the path doesn't exist 
Example:
const obj = { user: { profile: { name: "John" } } };
getValueByPath(obj, "user.profile.name"); // Returns: "John"
getValueByPath(obj, "user.profile.age");  // Returns: undefined
Object Expansion Check (isExpandableObject)
export const isExpandableObject = (value: JSONValue): boolean => {
  return (
    value !== null && 
    typeof value === 'object' && 
    !Array.isArray(value) && 
    Object.keys(value).length > 0
  );
};
Purpose: Determines if a value is a non-empty object that can be expanded in the UI.
Criteria for expandable objects:
- Not 
null - Is an object type
 - Not an array
 - Has at least one property
 
Data Filtering (filterData)
export const filterData = (data: JSONObject[], searchTerm: string): JSONObject[] => {
  if (!searchTerm.trim()) return data;
  
  let regex: RegExp;
  try {
    // Try to use the search term as a regex pattern
    regex = new RegExp(searchTerm, 'i');
  } catch (error) {
    // If invalid regex, use as literal string
    regex = new RegExp(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
  }
  
  return data.filter(item => searchInObject(item, regex));
};
Purpose: Filters data based on a search term with automatic regex support.
Smart regex handling:
- Attempts regex - Tries to use search term as a regular expression
 - Fallback to literal - If regex is invalid, escapes special characters for literal matching
 - Case insensitive - Uses the 
'i'flag for case-insensitive matching 
Recursive Search (searchInObject)
const searchInObject = (obj: JSONObject, regex: RegExp): boolean => {
  for (const [key, value] of Object.entries(obj)) {
    // Skip internal properties
    if (key.startsWith('__')) continue;
    
    // Search in string values
    if (typeof value === 'string' && regex.test(value)) {
      return true;
    }
    
    // Search in number values converted to string
    if (typeof value === 'number' && regex.test(String(value))) {
      return true;
    }
    
    // Search in nested objects and arrays
    if (value !== null && typeof value === 'object') {
      if (Array.isArray(value)) {
        for (const item of value) {
          if (typeof item === 'object' && item !== null && searchInObject(item as JSONObject, regex)) {
            return true;
          } else if ((typeof item === 'string' && regex.test(item)) || 
                     (typeof item === 'number' && regex.test(String(item)))) {
            return true;
          }
        }
      } else if (searchInObject(value as JSONObject, regex)) {
        return true;
      }
    }
  }
  
  return false;
};
Deep search capabilities:
- String matching - Direct regex test on string values
 - Number matching - Converts numbers to strings for searching
 - Nested objects - Recursively searches child objects
 - Array elements - Searches both primitive and object array elements
 - Skips internals - Ignores 
__prefixed properties 
Data Sorting (sortData)
export const sortData = (
  data: JSONObject[], 
  key: string, 
  direction: 'asc' | 'desc'
): JSONObject[] => {
  return [...data].sort((a, b) => {
    const valueA = getValueByPath(a, key);
    const valueB = getValueByPath(b, key);
    
    // Handle undefined or null values
    if (valueA === undefined || valueA === null) return direction === 'asc' ? -1 : 1;
    if (valueB === undefined || valueB === null) return direction === 'asc' ? 1 : -1;
    
    // Compare based on type
    if (typeof valueA === 'string' && typeof valueB === 'string') {
      return direction === 'asc' 
        ? valueA.localeCompare(valueB)
        : valueB.localeCompare(valueA);
    }
    
    if (typeof valueA === 'number' && typeof valueB === 'number') {
      return direction === 'asc' ? valueA - valueB : valueB - valueA;
    }
    
    // Convert to string for mixed types
    const strA = String(valueA);
    const strB = String(valueB);
    
    return direction === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
  });
};
Purpose: Sorts data by a specified field with proper type handling.
Sorting logic:
- Immutable - Creates a copy with 
[...data]before sorting - Path support - Uses 
getValueByPathfor nested field access - Null handling - Places null/undefined values at the beginning (asc) or end (desc)
 - Type-specific comparison:
- Strings - Uses 
localeCompare()for proper locale-aware sorting - Numbers - Uses arithmetic comparison for numerical sorting
 - Mixed types - Converts to strings as fallback
 
 - Strings - Uses 
 - Direction support - Reverses comparison for descending order
 
Key Design Principles
Immutability
Functions like sortData create new arrays rather than modifying input data, preventing unintended side effects.
Type Safety
All functions include proper TypeScript typing and runtime type checking to handle the dynamic nature of JSON data.
Error Resilience
Functions gracefully handle edge cases like null values, invalid regex patterns, and missing object properties.
Performance Optimization
- Uses 
Setfor deduplication inextractKeys - Implements early returns in search functions
 - Minimizes object traversal where possible
 
These utilities form the foundation for all data manipulation in the application, providing reliable and efficient processing of JSON data structures.