├── .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 | 
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 |
2 | |
3 |
4 |
5 |
30 |
--------------------------------------------------------------------------------
/webview-src/components/ArrayRow.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ index }} |
4 |
5 |
6 | |
7 | |
8 |
9 |
10 | |
11 | |
12 |
13 |
14 |
15 |
32 |
--------------------------------------------------------------------------------
/webview-src/components/ArrayTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | {{ hdr }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
38 |
--------------------------------------------------------------------------------
/webview-src/components/Cell.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Array[{{ element.length }}]
5 | {{ expanded ? '-' : '+' }}
6 |
7 |
8 |
9 |
10 |
11 | Object[{{ Object.keys( element ).length}}]
12 | {{ expanded ? '-' : '+' }}
13 |
14 |
15 |
16 | {{ element }}
17 |
18 |
19 |
39 |
--------------------------------------------------------------------------------
/webview-src/components/ObjectTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ key }} |
6 | | |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
--------------------------------------------------------------------------------
/webview-src/components/ResizableTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
20 | |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
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 |
--------------------------------------------------------------------------------