diff --git a/src/components/PostmanTable.js b/src/components/PostmanTable.js
new file mode 100644
index 00000000..a76cb9a8
--- /dev/null
+++ b/src/components/PostmanTable.js
@@ -0,0 +1,341 @@
+import React, { useState, useMemo } from 'react';
+import { ChevronLeft, Braces, List, Type, Hash, ToggleLeft, Minus } from 'lucide-react';
+
+const PostmanTable = ({ data, title = "JSON Data" }) => {
+ const [currentPath, setCurrentPath] = useState([]);
+ const [selectedRowIndex, setSelectedRowIndex] = useState(null);
+
+ // Get current data based on path
+ const getCurrentData = () => {
+ let current = data;
+ for (const pathSegment of currentPath) {
+ if (current && typeof current === 'object') {
+ current = current[pathSegment];
+ }
+ }
+ return current;
+ };
+
+ const currentData = getCurrentData();
+
+ // Check if current data is an array for horizontal table display
+ const isArrayView = Array.isArray(currentData);
+ const isObjectView = currentData && typeof currentData === 'object' && !Array.isArray(currentData);
+
+ // Generate table headers for array view
+ const getArrayHeaders = () => {
+ if (!isArrayView || currentData.length === 0) return [];
+
+ // Check if array contains objects or primitives
+ const firstItem = currentData[0];
+ if (firstItem && typeof firstItem === 'object' && !Array.isArray(firstItem)) {
+ // Array of objects - get all possible keys
+ const headers = new Set();
+ currentData.forEach(item => {
+ if (item && typeof item === 'object') {
+ Object.keys(item).forEach(key => headers.add(key));
+ }
+ });
+ return Array.from(headers);
+ } else {
+ // Array of primitives - use "Value" as header
+ return ['Value'];
+ }
+ };
+
+ const headers = getArrayHeaders();
+
+ // Handle row click - navigate to item details
+ const handleRowClick = (index, key = null) => {
+ if (isArrayView) {
+ setSelectedRowIndex(index);
+ setCurrentPath([...currentPath, index]);
+ } else if (isObjectView && key) {
+ // Navigate into object property
+ setCurrentPath([...currentPath, key]);
+ }
+ };
+
+ // Handle back navigation
+ const handleBack = () => {
+ if (currentPath.length > 0) {
+ const newPath = currentPath.slice(0, -1);
+ setCurrentPath(newPath);
+ setSelectedRowIndex(null);
+ }
+ };
+
+ // Handle breadcrumb navigation
+ const handleBreadcrumbClick = (index) => {
+ if (index === 0) {
+ // Click on "Root" - go to root
+ setCurrentPath([]);
+ } else {
+ // Click on specific breadcrumb - navigate to that level
+ // Adjust for the "Root" prefix in breadcrumb
+ const newPath = currentPath.slice(0, index);
+ setCurrentPath(newPath);
+ }
+ setSelectedRowIndex(null);
+ };
+
+ // Generate breadcrumb
+ const getBreadcrumb = () => {
+ const parts = ['Root'];
+ currentPath.forEach((segment, index) => {
+ if (typeof segment === 'number') {
+ parts.push(segment.toString());
+ } else {
+ parts.push(segment);
+ }
+ });
+ return parts;
+ };
+
+ // Format value for display
+ const formatValue = (value) => {
+ if (value === null) return 'null';
+ if (value === undefined) return 'undefined';
+ if (typeof value === 'string') return value;
+ if (typeof value === 'boolean') return value.toString();
+ if (typeof value === 'number') return value.toString();
+ if (typeof value === 'object') {
+ if (Array.isArray(value)) return `Array(${value.length})`;
+ return `Object(${Object.keys(value).length})`;
+ }
+ return String(value);
+ };
+
+ // Get type-specific styling and icon
+ const getTypeStyle = (value) => {
+ if (value === null) {
+ return {
+ color: 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-300',
+ icon: ,
+ type: 'null'
+ };
+ }
+
+ if (Array.isArray(value)) {
+ return {
+ color: 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-300',
+ icon:
,
+ type: 'array'
+ };
+ }
+
+ switch (typeof value) {
+ case 'object':
+ return {
+ color: 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-300',
+ icon: ,
+ type: 'object'
+ };
+ case 'string':
+ return {
+ color: 'bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-300',
+ icon: ,
+ type: 'string'
+ };
+ case 'number':
+ return {
+ color: 'bg-orange-100 text-orange-800 dark:bg-orange-900/20 dark:text-orange-300',
+ icon: ,
+ type: 'number'
+ };
+ case 'boolean':
+ return {
+ color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-300',
+ icon: ,
+ type: 'boolean'
+ };
+ default:
+ return {
+ color: 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-300',
+ icon: ,
+ type: 'unknown'
+ };
+ }
+ };
+
+ // Check if value is a complex object (not a primitive)
+ const isComplexValue = (value) => {
+ return value !== null && typeof value === 'object';
+ };
+
+ // Render value with appropriate styling
+ const renderValue = (value) => {
+ const typeStyle = getTypeStyle(value);
+ const formattedValue = formatValue(value);
+
+ return (
+
+ {typeStyle.icon}
+ {formattedValue}
+
+ );
+ };
+
+ // Get value type
+ const getValueType = (value) => {
+ if (value === null) return 'null';
+ if (Array.isArray(value)) return 'array';
+ return typeof value;
+ };
+
+ return (
+
+ {/* Header with Breadcrumb */}
+
+
+
+ {currentPath.length > 0 && (
+
+ )}
+
+ {getBreadcrumb().map((part, index) => (
+
+ {index > 0 && /}
+
+
+ ))}
+
+
+
+ {isArrayView && `${currentData.length} items`}
+ {isObjectView && `${Object.keys(currentData).length} properties`}
+
+
+
+
+ {/* Content */}
+
+ {isArrayView ? (
+ // Horizontal table for arrays
+
+
+
+ |
+ #
+ |
+ {headers.map(header => (
+
+ {header}
+ |
+ ))}
+
+
+
+ {currentData.map((item, index) => {
+ const isPrimitiveArray = headers.length === 1 && headers[0] === 'Value';
+ return (
+ handleRowClick(index)}
+ className="hover:bg-blue-50 dark:hover:bg-blue-900/20 cursor-pointer transition-colors duration-150"
+ >
+ |
+ {index}
+ |
+ {isPrimitiveArray ? (
+
+ {renderValue(item)}
+ |
+ ) : (
+ headers.map(header => (
+
+ {renderValue(item?.[header])}
+ |
+ ))
+ )}
+
+ );
+ })}
+
+
+ ) : isObjectView ? (
+ // Vertical key-value table for objects
+
+
+
+ |
+ Key
+ |
+
+ Value
+ |
+
+
+
+ {Object.entries(currentData).map(([key, value]) => {
+ const isClickable = typeof value === 'object' && value !== null;
+ return (
+ isClickable && handleRowClick(null, key)}
+ className={`transition-colors duration-150 ${
+ isClickable
+ ? 'hover:bg-blue-50 dark:hover:bg-blue-900/20 cursor-pointer'
+ : 'hover:bg-gray-50 dark:hover:bg-gray-700'
+ }`}
+ >
+ |
+ {key}
+ |
+
+ {renderValue(value)}
+ |
+
+ );
+ })}
+
+
+ ) : (
+ // Fallback for primitive values
+
+
+
+ {formatValue(currentData)}
+
+
+ Type: {getValueType(currentData)}
+
+
+
+ )}
+
+
+ {/* Footer */}
+
+
+
+ {isArrayView ? `Array with ${currentData.length} items` :
+ isObjectView ? `Object with ${Object.keys(currentData).length} properties` :
+ `Primitive value (${getValueType(currentData)})`}
+
+
+ Path: {currentPath.length === 0 ? 'Root' : currentPath.join(' → ')}
+
+
+
+
+ );
+};
+
+export default PostmanTable;
diff --git a/src/components/PostmanTreeTable.js b/src/components/PostmanTreeTable.js
new file mode 100644
index 00000000..40c7ca8e
--- /dev/null
+++ b/src/components/PostmanTreeTable.js
@@ -0,0 +1,273 @@
+import React, { useState, useMemo } from 'react';
+import { ChevronRight, ChevronDown, Copy, Search, Filter } from 'lucide-react';
+import CopyButton from './CopyButton';
+
+const PostmanTreeTable = ({ data, title = "JSON Data" }) => {
+ const [expandedPaths, setExpandedPaths] = useState(new Set());
+ const [searchTerm, setSearchTerm] = useState('');
+ const [filterType, setFilterType] = useState('all');
+
+ // Flatten the data structure for table display
+ const flattenData = (obj, path = '', level = 0) => {
+ const result = [];
+
+ if (obj === null || obj === undefined) {
+ return [{
+ key: path || 'root',
+ value: obj,
+ type: obj === null ? 'null' : 'undefined',
+ level,
+ path,
+ hasChildren: false
+ }];
+ }
+
+ if (typeof obj !== 'object') {
+ return [{
+ key: path || 'root',
+ value: obj,
+ type: typeof obj,
+ level,
+ path,
+ hasChildren: false
+ }];
+ }
+
+ if (Array.isArray(obj)) {
+ const arrayItem = {
+ key: path || 'root',
+ value: `Array(${obj.length})`,
+ type: 'array',
+ level,
+ path,
+ hasChildren: obj.length > 0,
+ isExpandable: true
+ };
+ result.push(arrayItem);
+
+ if (expandedPaths.has(path)) {
+ obj.forEach((item, index) => {
+ const itemPath = path ? `${path}[${index}]` : `[${index}]`;
+ result.push(...flattenData(item, itemPath, level + 1));
+ });
+ }
+ } else {
+ const keys = Object.keys(obj);
+ const objectItem = {
+ key: path || 'root',
+ value: `Object(${keys.length})`,
+ type: 'object',
+ level,
+ path,
+ hasChildren: keys.length > 0,
+ isExpandable: true
+ };
+
+ if (path) result.push(objectItem);
+
+ if (!path || expandedPaths.has(path)) {
+ keys.forEach(key => {
+ const keyPath = path ? `${path}.${key}` : key;
+ result.push(...flattenData(obj[key], keyPath, path ? level + 1 : level));
+ });
+ }
+ }
+
+ return result;
+ };
+
+ const toggleExpanded = (path) => {
+ const newExpanded = new Set(expandedPaths);
+ if (newExpanded.has(path)) {
+ newExpanded.delete(path);
+ } else {
+ newExpanded.add(path);
+ }
+ setExpandedPaths(newExpanded);
+ };
+
+ const flatData = useMemo(() => flattenData(data), [data, expandedPaths]);
+
+ const filteredData = useMemo(() => {
+ return flatData.filter(item => {
+ // Search filter
+ if (searchTerm) {
+ const searchLower = searchTerm.toLowerCase();
+ const keyMatch = item.key.toLowerCase().includes(searchLower);
+ const valueMatch = String(item.value).toLowerCase().includes(searchLower);
+ if (!keyMatch && !valueMatch) return false;
+ }
+
+ // Type filter
+ if (filterType !== 'all' && item.type !== filterType) {
+ return false;
+ }
+
+ return true;
+ });
+ }, [flatData, searchTerm, filterType]);
+
+ const getTypeColor = (type) => {
+ const colors = {
+ string: 'bg-green-100 text-green-800',
+ number: 'bg-blue-100 text-blue-800',
+ boolean: 'bg-purple-100 text-purple-800',
+ object: 'bg-orange-100 text-orange-800',
+ array: 'bg-red-100 text-red-800',
+ null: 'bg-gray-100 text-gray-800',
+ undefined: 'bg-gray-100 text-gray-800'
+ };
+ return colors[type] || 'bg-gray-100 text-gray-800';
+ };
+
+ const formatValue = (value, type) => {
+ if (type === 'string') return `"${value}"`;
+ if (type === 'null') return 'null';
+ if (type === 'undefined') return 'undefined';
+ if (type === 'boolean') return value ? 'true' : 'false';
+ return String(value);
+ };
+
+ const getKeyDisplay = (key, level) => {
+ const parts = key.split(/[.\[\]]+/).filter(Boolean);
+ return parts[parts.length - 1] || key;
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
{title}
+
+ {/* Search */}
+
+
+ setSearchTerm(e.target.value)}
+ className="pl-10 pr-4 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
+ />
+
+
+ {/* Type Filter */}
+
+
+
+
+
+
+
+
+ {/* Table */}
+
+
+
+
+ |
+ Key
+ |
+
+ Type
+ |
+
+ Value
+ |
+
+ Actions
+ |
+
+
+
+ {filteredData.map((item, index) => (
+
+ {/* Key Column */}
+
+
+ {item.isExpandable ? (
+
+ ) : (
+
+ )}
+
+ {getKeyDisplay(item.key, item.level)}
+
+
+ |
+
+ {/* Type Column */}
+
+
+ {item.type}
+
+ |
+
+ {/* Value Column */}
+
+
+ {formatValue(item.value, item.type)}
+
+ |
+
+ {/* Actions Column */}
+
+ {!item.isExpandable && (
+
+
+
+ )}
+ |
+
+ ))}
+
+
+
+
+ {/* Footer */}
+
+
+
+ Showing {filteredData.length} of {flatData.length} items
+
+
+ {Object.keys(data || {}).length} root properties
+
+
+
+
+ );
+};
+
+export default PostmanTreeTable;
diff --git a/src/components/StructuredEditor.js b/src/components/StructuredEditor.js
index b69851ae..a6fc267e 100644
--- a/src/components/StructuredEditor.js
+++ b/src/components/StructuredEditor.js
@@ -232,14 +232,67 @@ const StructuredEditor = ({ onDataChange, initialData = {} }) => {
};
const getTypeIcon = (value) => {
- if (value === null) return ∅;
- if (value === undefined) return ?;
- if (typeof value === 'string') return ;
- if (typeof value === 'number') return ;
- if (typeof value === 'boolean') return ;
- if (Array.isArray(value)) return
;
- if (typeof value === 'object') return ;
- return ;
+ if (value === null) {
+ return (
+
+ -
+
+ );
+ }
+
+ if (value === undefined) {
+ return (
+
+ ?
+
+ );
+ }
+
+ if (typeof value === 'string') {
+ return (
+
+
+
+ );
+ }
+
+ if (typeof value === 'number') {
+ return (
+
+
+
+ );
+ }
+
+ if (typeof value === 'boolean') {
+ return (
+
+
+
+ );
+ }
+
+ if (Array.isArray(value)) {
+ return (
+
+
+
+ );
+ }
+
+ if (typeof value === 'object') {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
};
const renderValue = (value, key, path, parentPath) => {
diff --git a/src/pages/ObjectEditor.js b/src/pages/ObjectEditor.js
index d72cb8d5..fa50c1c5 100644
--- a/src/pages/ObjectEditor.js
+++ b/src/pages/ObjectEditor.js
@@ -1,9 +1,10 @@
import React, { useState, useRef, useCallback } from 'react';
-import { Edit3, Upload, FileText, Map } from 'lucide-react';
+import { Edit3, Upload, FileText, Map, Table } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
import StructuredEditor from '../components/StructuredEditor';
import MindmapView from '../components/MindmapView';
+import PostmanTable from '../components/PostmanTable';
const ObjectEditor = () => {
console.log(' ObjectEditor component loaded successfully!');
@@ -13,7 +14,8 @@ const ObjectEditor = () => {
const [inputFormat, setInputFormat] = useState('');
const [inputValid, setInputValid] = useState(false);
const [error, setError] = useState('');
- const [viewMode, setViewMode] = useState('visual'); // 'visual', 'mindmap'
+ const [viewMode, setViewMode] = useState('visual'); // 'visual', 'mindmap', 'table'
+ const [showOutputs, setShowOutputs] = useState(false);
const [outputs, setOutputs] = useState({
jsonPretty: '',
jsonMinified: '',
@@ -293,20 +295,43 @@ const ObjectEditor = () => {
// Load sample data
const loadSample = () => {
const sample = {
- "user": {
- "name": "John Doe",
- "age": 30,
- "email": "john@example.com",
- "preferences": {
- "theme": "dark",
- "notifications": true
+ "users": [
+ {
+ "id": 1,
+ "name": "John Doe",
+ "email": "john@example.com",
+ "age": 30,
+ "role": "admin",
+ "active": true
},
- "tags": ["developer", "javascript", "react"]
- },
+ {
+ "id": 2,
+ "name": "Jane Smith",
+ "email": "jane@example.com",
+ "age": 28,
+ "role": "user",
+ "active": true
+ },
+ {
+ "id": 3,
+ "name": "Bob Wilson",
+ "email": "bob@example.com",
+ "age": 35,
+ "role": "moderator",
+ "active": false
+ }
+ ],
"settings": {
+ "theme": "dark",
"language": "en",
- "timezone": "UTC"
- }
+ "timezone": "UTC",
+ "features": {
+ "notifications": true,
+ "darkMode": true,
+ "autoSave": false
+ }
+ },
+ "tags": ["developer", "javascript", "react", "nodejs"]
};
setStructuredData(sample);
generateOutputs(sample);
@@ -382,6 +407,17 @@ const ObjectEditor = () => {
Mindmap View
+
{/* Input Section */}
@@ -420,6 +456,7 @@ const ObjectEditor = () => {
{viewMode === 'visual' && 'Visual Editor'}
{viewMode === 'mindmap' && 'Mindmap Visualization'}
+ {viewMode === 'table' && 'Table View'}
{viewMode === 'visual' && (
@@ -435,10 +472,29 @@ const ObjectEditor = () => {
)}
+ {viewMode === 'table' && (
+
+ )}
+
+
+
+ {/* Toggle Output Button */}
+
+
{/* Output Actions */}
-
+ {showOutputs && (
+
{/* JSON Pretty */}
+ )}
{/* Usage Tips */}
@@ -494,10 +551,12 @@ const ObjectEditor = () => {
- • Visual Editor: Create and modify object structures with forms
- • Mindmap View: Visualize complex JSON structures as interactive diagrams
+ - • Table View: Browse data like Postman - click arrays for horizontal tables, objects for key-value pairs
+ - • Navigation: Use breadcrumbs and Back button to navigate through nested data structures
- • Input Data: Paste JSON/PHP serialized data with auto-detection in the input field
- • Import data from files or use the sample data to get started
+ - • Toggle output formats visibility with the "Show/Hide Output Formats" button
- • Export your data in any format: JSON pretty, minified, or PHP serialized
- - • Use copy buttons to quickly copy any output format