├── .gitignore ├── .vscodeignore ├── LICENSE ├── README.md ├── babel.config.js ├── demo.webp ├── editor ├── json-grid-editor-provider.js ├── json-grid-viewer.js ├── main.js └── util.js ├── icon.png ├── package-lock.json ├── package.json ├── vue.config.js └── webview-src ├── App.vue ├── components ├── ArrayRow.vue ├── ArrayTable.vue ├── Cell.vue ├── ObjectTable.vue └── ResizableTable.vue ├── css └── app.css └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /webview 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | webview-src 3 | babel.config.js 4 | vue.config.js 5 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Igor Honhoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json-grid-viewer 2 | This extension allows you get a better overview of the content in a JSON file by showing it in a resizable grid. 3 | - Columns are resizable. 4 | - Each object and array is collapsed by default but can be expanded to see all contents 5 | - Arrays of objects show in a table format 6 | 7 | ## Demo 8 | ![demo](./demo.webp) 9 | 10 | ## Usage 11 | To open a json file in the grid viewer, right click the file, select *Open With... > JSON Grid*. The grid is read only but will display any changes made to the json file live, provided the json is valid. 12 | 13 | ## To do: 14 | - Add editing capabilities 15 | - Take colours from the active theme 16 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dutchigor/json-grid-viewer/4b204f42c554dca8a8bddd6ddcfa16d42f26fc19/demo.webp -------------------------------------------------------------------------------- /editor/json-grid-editor-provider.js: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode') 2 | const { JsonGridViewer } = require('./json-grid-viewer') 3 | 4 | class JsonGridEditorProvider { 5 | 6 | constructor(context) { 7 | this.context = context 8 | } 9 | 10 | static register(context) { 11 | const viewType = 'jsonGridViewer.json' 12 | const providerRegistration = vscode.window.registerCustomEditorProvider( 13 | viewType, new JsonGridEditorProvider(context) 14 | ) 15 | return providerRegistration 16 | } 17 | 18 | async resolveCustomTextEditor(document, webviewPanel, _token) { 19 | // Initialise viewer 20 | const jsonGridViewer = new JsonGridViewer(document, webviewPanel, this.context) 21 | 22 | // Make sure we clean up when our editor is closed. 23 | webviewPanel.onDidDispose(() => { 24 | jsonGridViewer.cleanup() 25 | jsonGridViewer = null 26 | }); 27 | } 28 | } 29 | 30 | module.exports.JsonGridEditorProvider = JsonGridEditorProvider 31 | -------------------------------------------------------------------------------- /editor/json-grid-viewer.js: -------------------------------------------------------------------------------- 1 | const vscode = require( 'vscode' ) 2 | const path = require( 'path' ) 3 | const getNonce = require( './util' ).getNonce 4 | 5 | class JsonGridViewer { 6 | constructor( document, webviewPanel, context ) { 7 | this.document = document 8 | this.webviewPanel = webviewPanel 9 | this.context = context 10 | 11 | // Setup initial content for the webview 12 | this.webviewPanel.webview.options = { 13 | enableScripts: true, 14 | } 15 | 16 | this.webviewPanel.webview.html = this.getHtmlForWebview() 17 | 18 | // Create document change listener to update the webview 19 | this.changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => { 20 | if (e.document.uri.toString() === this.document.uri.toString()) { 21 | this.updateWebview() 22 | } 23 | }); 24 | 25 | // Create listener to process messages from the webview 26 | this.webviewPanel.webview.onDidReceiveMessage( msg => { 27 | switch (msg.type) { 28 | case 'ready': 29 | this.updateWebview() 30 | break; 31 | } 32 | }) 33 | 34 | } 35 | 36 | getHtmlForWebview() { 37 | // Local path to script and css for the webview 38 | const appUri = this.webviewPanel.webview.asWebviewUri( vscode.Uri.file( 39 | path.join( this.context.extensionPath, 'webview', 'js', 'app.js' ) 40 | )) 41 | const chunkVendorsUri = this.webviewPanel.webview.asWebviewUri( vscode.Uri.file( 42 | path.join( this.context.extensionPath, 'webview', 'js', 'chunk-vendors.js' ) 43 | )) 44 | const appCssUri = this.webviewPanel.webview.asWebviewUri( vscode.Uri.file( 45 | path.join( this.context.extensionPath, 'webview', 'css', 'app.css' ) 46 | )) 47 | 48 | const nonce = getNonce() 49 | 50 | return ` 51 | 52 | 53 | 54 | 55 | 56 | 61 | JSON Grid viewer 62 | 63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 | ` 71 | } 72 | 73 | // Hook up event handlers so that we can synchronize the webview with the text document. 74 | updateWebview() { 75 | let doc 76 | try { 77 | doc = JSON.parse( this.document.getText() ) 78 | } catch (error) { 79 | return 80 | } 81 | this.webviewPanel.webview.postMessage({ 82 | type: 'update', 83 | doc 84 | }) 85 | } 86 | 87 | // remove any listeners 88 | cleanup() { 89 | this.changeDocumentSubscription.dispose() 90 | } 91 | } 92 | 93 | exports.JsonGridViewer = JsonGridViewer 94 | -------------------------------------------------------------------------------- /editor/main.js: -------------------------------------------------------------------------------- 1 | const { JsonGridEditorProvider } = require( "./json-grid-editor-provider" ) 2 | 3 | exports.activate = function ( context ) { 4 | context.subscriptions.push( JsonGridEditorProvider.register( context ) ) 5 | } 6 | -------------------------------------------------------------------------------- /editor/util.js: -------------------------------------------------------------------------------- 1 | module.exports.getNonce = function () { 2 | let text = ''; 3 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 | for (let i = 0; i < 32; i++) { 5 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 6 | } 7 | return text; 8 | } 9 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dutchigor/json-grid-viewer/4b204f42c554dca8a8bddd6ddcfa16d42f26fc19/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-viewer", 3 | "publisher": "DutchIgor", 4 | "displayName": "JSON Grid Viewer", 5 | "description": "Get a better overview of the content in a JSON file by viewing it in an resizable grid.", 6 | "icon": "icon.png", 7 | "galleryBanner": { 8 | "color": "#d4e1ef", 9 | "theme": "light" 10 | }, 11 | "version": "0.1.0", 12 | "repository": "github:dutchigor/json-grid-viewer", 13 | "engines": { 14 | "vscode": "^1.46.0" 15 | }, 16 | "categories": [ 17 | "Visualization" 18 | ], 19 | "keywords": [ 20 | "json", 21 | "drilldown", 22 | "hierarchy", 23 | "grid", 24 | "overview" 25 | ], 26 | "qna": "https://github.com/dutchigor/json-grid-viewer/discussions", 27 | "main": "./editor/main.js", 28 | "activationEvents": [ 29 | "onCustomEditor:jsonGridViewer.json" 30 | ], 31 | "contributes": { 32 | "customEditors": [ 33 | { 34 | "viewType": "jsonGridViewer.json", 35 | "displayName": "JSON Grid", 36 | "selector": [ 37 | { 38 | "filenamePattern": "*.json" 39 | } 40 | ], 41 | "priority": "option" 42 | } 43 | ] 44 | }, 45 | "scripts": { 46 | "serve": "vue-cli-service serve", 47 | "build": "vue-cli-service build", 48 | "lint": "vue-cli-service lint", 49 | "vscode:prepublish": "npm run build" 50 | }, 51 | "dependencies": { 52 | "core-js": "^3.18.0", 53 | "vue": "^3.2.13" 54 | }, 55 | "devDependencies": { 56 | "@types/vscode": "^1.46.0", 57 | "@vue/cli-plugin-babel": "~4.5.13", 58 | "@vue/cli-plugin-eslint": "~4.5.13", 59 | "@vue/cli-service": "~4.5.13", 60 | "@vue/compiler-sfc": "^3.2.13", 61 | "babel-eslint": "^10.1.0", 62 | "eslint": "^6.8.0", 63 | "eslint-plugin-vue": "^7.0.0-0" 64 | }, 65 | "eslintConfig": { 66 | "root": true, 67 | "env": { 68 | "node": true 69 | }, 70 | "extends": [ 71 | "plugin:vue/vue3-essential", 72 | "eslint:recommended" 73 | ], 74 | "parserOptions": { 75 | "parser": "babel-eslint" 76 | }, 77 | "rules": {} 78 | }, 79 | "license": "SEE LICENSE IN LICENSE" 80 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | chainWebpack: config => { 3 | config 4 | .entry("app") 5 | .clear() 6 | .add("./webview-src/main.js") 7 | .end() 8 | config.plugins.delete('html') 9 | config.plugins.delete('preload') 10 | config.plugins.delete('prefetch') 11 | }, 12 | outputDir: 'webview', 13 | filenameHashing: false 14 | }; -------------------------------------------------------------------------------- /webview-src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 30 | -------------------------------------------------------------------------------- /webview-src/components/ArrayRow.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | -------------------------------------------------------------------------------- /webview-src/components/ArrayTable.vue: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 38 | -------------------------------------------------------------------------------- /webview-src/components/Cell.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 39 | -------------------------------------------------------------------------------- /webview-src/components/ObjectTable.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | -------------------------------------------------------------------------------- /webview-src/components/ResizableTable.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 75 | 76 | -------------------------------------------------------------------------------- /webview-src/css/app.css: -------------------------------------------------------------------------------- 1 | .vscode-light { 2 | --object-color: #267F99; 3 | --array-color: #795E26; 4 | --string-color: #A31515; 5 | --number-color: #098658; 6 | --literal-color: #0000FF; 7 | --identifier-color: #001080; 8 | } 9 | 10 | .vscode-dark, .vscode-high-contrast { 11 | --object-color: #4EC9B0; 12 | --array-color: #DCDCAA; 13 | --string-color: #CE9178; 14 | --number-color: #B5CEA8; 15 | --literal-color: #569CD6; 16 | --identifier-color: #9CDCFE; 17 | } 18 | 19 | body { 20 | font-family: var(--vscode-editor-font-family); 21 | font-weight: var(--vscode-editor-font-weight); 22 | font-size: var(--vscode-editor-font-size); 23 | } 24 | table { 25 | margin: 2px 0; 26 | border-collapse: collapse; 27 | } 28 | td, th { 29 | padding: 2px 4px; 30 | vertical-align: baseline; 31 | } 32 | td.member, th.key, td.element, td.value { 33 | border: 1px solid var(--vscode-editor-foreground); 34 | } 35 | .index, .collapsed { 36 | white-space: nowrap; 37 | } 38 | .index::after { 39 | margin-left: .2em; 40 | } 41 | td.index::after, .badge { 42 | padding: 1px 3px; 43 | display: inline-block; 44 | border-radius: 0.5em; 45 | color: var(--vscode-editor-background); 46 | } 47 | td.index.object::after { 48 | content: 'Obj'; 49 | } 50 | td.index.object::after, .object.badge { 51 | background-color: var(--object-color); 52 | } 53 | td.index.array::after { 54 | content: 'Arr'; 55 | } 56 | td.index.array::after, .array.badge { 57 | background-color: var(--array-color); 58 | } 59 | td.index.string::after { 60 | content: 'Str'; 61 | background-color: var(--string-color); 62 | } 63 | td.index.number::after { 64 | content: 'Num'; 65 | background-color: var(--number-color) 66 | } 67 | td.index.boolean::after { 68 | content: 'Bool'; 69 | background-color: var(--literal-color); 70 | } 71 | td.index.null::after { 72 | content: 'Null'; 73 | background-color: var(--literal-color); 74 | } 75 | .value.string { 76 | color: var(--string-color); 77 | } 78 | .value.number { 79 | color: var(--number-color); 80 | } 81 | .value.boolean { 82 | color: var(--literal-color); 83 | } 84 | .value.null::before { 85 | content: 'null'; 86 | color: var(--literal-color); 87 | } 88 | .array-el:not(.object) .value { 89 | width: 100%; 90 | } 91 | .expand { 92 | display: inline-block; 93 | text-align: center; 94 | width: 1em; 95 | margin-left: 4px; 96 | cursor: pointer; 97 | border: 1px solid; 98 | border-radius: 3px; 99 | } 100 | th.member { 101 | border: 1px solid var(--vscode-editor-foreground); 102 | border-top: 2px solid var(--vscode-editor-foreground); 103 | border-bottom: 2px solid var(--vscode-editor-foreground); 104 | } 105 | th.key, th.member { 106 | color: var(--identifier-color); 107 | text-align: left; 108 | } 109 | table.object { 110 | border: 2px solid; 111 | } 112 | th.index:first-child, th:last-child, td.index, td.member:last-child, td.value:last-child { 113 | border-right: 2px solid var(--vscode-editor-foreground); 114 | } 115 | tr.array-el:last-child td.member, tr.array-el:last-child td.value { 116 | border-bottom: 2px solid var(--vscode-editor-foreground); 117 | } 118 | th.object.key, td.object.element { 119 | border-top: 0; 120 | } 121 | tr.object-hdr th { 122 | padding: 0; 123 | border: 0; 124 | } -------------------------------------------------------------------------------- /webview-src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | 3 | import './css/app.css' 4 | 5 | import App from './App.vue' 6 | import ObjectTable from './components/ObjectTable' 7 | import ArrayTable from './components/ArrayTable' 8 | import Cell from './components/Cell' 9 | import ResizableTable from './components/ResizableTable' 10 | import ArrayRow from './components/ArrayRow' 11 | 12 | const app = createApp(App) 13 | app.component( 'ObjectTable', ObjectTable ) 14 | app.component( 'ArrayTable', ArrayTable ) 15 | app.component( 'Cell', Cell ) 16 | app.component( 'ResizableTable', ResizableTable ) 17 | app.component( 'ArrayRow', ArrayRow ) 18 | app.mount('#app') 19 | --------------------------------------------------------------------------------