├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── MANIFEST.in ├── README.md ├── jsoneditor.desktop ├── jsoneditor ├── __init__.py ├── __main__.py ├── files │ ├── img │ │ ├── checkmark.svg │ │ ├── close.svg │ │ ├── favicon.ico │ │ └── jsoneditor-icons.svg │ ├── index.css │ ├── index.html │ ├── index.js │ ├── jsoneditor.min.css │ └── jsoneditor.min.js └── jsoneditor.py ├── requirements.txt ├── setup.py └── tests ├── test.csv ├── test.json └── test.py /.gitattributes: -------------------------------------------------------------------------------- 1 | jsoneditor/files/jsoneditor* linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *__pycache__ 3 | build/ 4 | dist/ 5 | *.egg-info/ 6 | 7 | 8 | env/ 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "js-jsoneditor"] 2 | path = js-jsoneditor 3 | url = git@github.com:dermasmid/jsoneditor.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cheskel Twersky 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include jsoneditor/files/ * 2 | include requirements.txt 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💥py-jsoneditor💥 2 | Quickly View and Edit any JSON data. 3 | 4 | 5 | # Why? 6 | 7 | When working with JSON data, You often need to get a structured view of the JSON in order to be able to work with it. There's an online tool [https://jsoneditoronline.org/](https://jsoneditoronline.org/) which I used for this, but copying/pasting all the time got frustrating pretty quickly, This is why I created this package which you can launch right from Python or from the command line. 8 | 9 | 10 | # Screenshot 11 | 12 | ![](https://res.cloudinary.com/dermasmid/image/upload/v1624745064/Screenshot_from_2021-06-27_01-02-58_qymcrb.png) 13 | 14 | 15 | # Installation 16 | 17 | ```bash 18 | pip install jsoneditor 19 | ``` 20 | 21 | 22 | # Python example 23 | 24 | In python you can simply import `jsoneditor` and call the `editjson` function, the first argument is going to be the data. See [Formats you can pass the JSON as](#formats-you-can-pass-the-json-as) for all the formats you can pass the JSON in. See [Python api](#python-api) for a full list of addtional arguments that you can pass to `editjson`. 25 | ```python 26 | import requests 27 | import jsoneditor 28 | 29 | data = requests.get('https://jsonplaceholder.typicode.com/comments').json() 30 | jsoneditor.editjson(data) 31 | ``` 32 | 33 | 34 | # Command line example 35 | 36 | From the command line you can either pass the data as an argument as so: 37 | ```bash 38 | jsoneditor '{"Hey": "Hi"}' 39 | ``` 40 | Or you can pipe it in like so: 41 | ```bash 42 | curl https://jsonplaceholder.typicode.com/comments | jsoneditor 43 | ``` 44 | Or you can use what you have in your clipboard like so: 45 | ```bash 46 | jsoneditor -c 47 | ``` 48 | See [Formats you can pass the JSON as](#formats-you-can-pass-the-json-as) for all the formats you can pass the JSON in. 49 | 50 | Refer to [CLI options](#cli-options) for a list of all cli options. Alternatively you can run `jsoneditor --help` from your terminal to see it. 51 | 52 | 53 | ## Formats you can pass the JSON as 54 | 55 | You can pass the json in any of the following formats: 56 | * as valid json string. Example: `{"Hey": "Hi"}` 57 | * as a python dict. Example: `{'Hey': 'hi'}` 58 | * as a url the points to valid json. Example: `https://jsonplaceholder.typicode.com/comments` 59 | * as a file path that is valid json. Example: `data.json` 60 | 61 | 62 | ## Python Api 63 | 64 | | parameter | type | optional |description | 65 | | --------- | ------- | -------- |-----------------------------------------------------------------------------| 66 | | `data` | `Any` | ❌ | The data in any of [these](#formats-you-can-pass-the-json-as) formats. | 67 | | `callback`| `callable`| ✔️ | If you provide this argument you will have a ✅ button which will trigger this callback.| 68 | | `options` | `dict` | ✔️ | Options to pass the the jsoneditor object. See [here](https://github.com/josdejong/jsoneditor/blob/master/docs/api.md#configuration-options)| 69 | | `additional_js`| `str`| ✔️ | You can pass some JavaScript to run on the client side. You can interact with the editor by accessing the `window.editor` variable.| 70 | | `keep_running`| `bool` | ✔️ | Whether to keep the server running. Defaults to `False`. | 71 | | `run_in_thread`| `bool` | ✔️ | Whether to run the server in a separate thread. Defaults to `False`. | 72 | | `is_csv`| `bool` | ✔️ | Whether the data is csv data. Defaults to `False`. | 73 | | `is_yaml`| `bool` | ✔️ | Whether the data is yaml data. Defaults to `False`. | 74 | | `is_ndjson`| `bool` | ✔️ | Whether the data is Newline Delimited JSON . Defaults to `False`. | 75 | | `is_js_object`| `bool` | ✔️ | Whether the data is a JavaScript Object. Defaults to `False`. | 76 | | `title`| `str` | ✔️ | A title to display in the browser. | 77 | | `port`| `int` | ✔️ | specify which port to use. | 78 | 79 | 80 | ## CLI options 81 | 82 | | parameter | description | 83 | | --------- | ----------------------------------------------------------------------| 84 | | `data` | The data in any of [these](#formats-you-can-pass-the-json-as) formats | 85 | | `-o` | Add a button that will output the json back to the console | 86 | | `-b` | Keep running in background | 87 | | `-c` | Get JSON input from clipboard | 88 | | `-k` | Keep alive | 89 | | `-e` | Edit mode | 90 | | `-n` | Don't launch browser | 91 | | `-p` | Server port | 92 | | `--out` | File to output when in edit mode | 93 | | `-t` | Title to display in browser window | 94 | | `--csv` | Input is CSV | 95 | | `--yaml` | Input is YAML | 96 | | `--js` | Input is a JavaScript Object | 97 | | `--ndjson`| Input is Newline Delimited JSON | 98 | 99 | 100 | ## Build 101 | 102 | ```bash 103 | python setup.py sdist 104 | ``` 105 | 106 | # Acknowledgements 107 | 108 | * [jsoneditor](https://github.com/josdejong/jsoneditor) 109 | -------------------------------------------------------------------------------- /jsoneditor.desktop: -------------------------------------------------------------------------------- 1 | # ~/.local/share/applications 2 | [Desktop Entry] 3 | Exec=jsoneditor %f 4 | Name=jsoneditor 5 | Terminal=true 6 | Type=Application 7 | -------------------------------------------------------------------------------- /jsoneditor/__init__.py: -------------------------------------------------------------------------------- 1 | from .jsoneditor import editjson, main 2 | 3 | __version__ = "1.6.0" 4 | -------------------------------------------------------------------------------- /jsoneditor/__main__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /jsoneditor/files/img/checkmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jsoneditor/files/img/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jsoneditor/files/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dermasmid/py-jsoneditor/ca87d6eb11e6c84ae91801178f8e64b48ddf35c8/jsoneditor/files/img/favicon.ico -------------------------------------------------------------------------------- /jsoneditor/files/img/jsoneditor-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | JSON Editor Icons 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | JSON Editor Icons 27 | 28 | 29 | 30 | 32 | 56 | 60 | 61 | 62 | 69 | 76 | 83 | 90 | 97 | 100 | 107 | 114 | 115 | 119 | 126 | 133 | 134 | 141 | 148 | 155 | 157 | 164 | 171 | 178 | 179 | 182 | 189 | 196 | 203 | 204 | 211 | 217 | 223 | 230 | 235 | 240 | 247 | 253 | 258 | 265 | 271 | 277 | 284 | 291 | 298 | 305 | 312 | 319 | 326 | 332 | 340 | 346 | 352 | 359 | 367 | 375 | 382 | 389 | 396 | 403 | 410 | 417 | 424 | 431 | 437 | 445 | 451 | 457 | 464 | 472 | 480 | 487 | 494 | 501 | 508 | 515 | 522 | 529 | 536 | 541 | 546 | 551 | 557 | 563 | 568 | 573 | 578 | 583 | 588 | 604 | 621 | 638 | 655 | 661 | 667 | 673 | 679 | 686 | 692 | 695 | 702 | 709 | 716 | 723 | 729 | 730 | 736 | 743 | 749 | 750 | -------------------------------------------------------------------------------- /jsoneditor/files/index.css: -------------------------------------------------------------------------------- 1 | .jsoneditor-menu .send-back-json { 2 | background: url("img/checkmark.svg"); 3 | } 4 | 5 | .jsoneditor-menu .finish-and-shutdown { 6 | background: url("img/close.svg"); 7 | } 8 | -------------------------------------------------------------------------------- /jsoneditor/files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /jsoneditor/files/index.js: -------------------------------------------------------------------------------- 1 | var editor 2 | 3 | function init(data) { 4 | const container = document.getElementById("jsoneditor"); 5 | const options = data.options; 6 | editor = new JSONEditor(container, options); 7 | const initialJson = data.data; 8 | const jsoneditorMenu = document.getElementsByClassName('jsoneditor-menu')[0] 9 | editor.set(initialJson); 10 | document.title = data.title 11 | if (data.callback) { 12 | addCallbackButton(jsoneditorMenu, editor) 13 | } 14 | if (data.keep_running) { 15 | addCloseButton(jsoneditorMenu); 16 | } 17 | if (data.additional_js) { 18 | eval(data.additional_js) 19 | } 20 | if (!data.keep_running) { 21 | setTimeout(() => makeSureServerIsClosed(), 1000) 22 | } 23 | } 24 | 25 | 26 | function makeSureServerIsClosed() { 27 | let xhr = new XMLHttpRequest(); 28 | xhr.open("GET", window.location.origin + '/close'); 29 | xhr.onerror = () => {} 30 | xhr.send(null); 31 | } 32 | 33 | 34 | function addCallbackButton(jsoneditorMenu, editor) { 35 | const callbackButton = document.createElement('button') 36 | callbackButton.className = 'send-back-json' 37 | callbackButton.title = 'Trigger callback with the current data' 38 | jsoneditorMenu.append(callbackButton) 39 | callbackButton.onclick = () => { 40 | var xhr = new XMLHttpRequest(); 41 | xhr.open("POST", window.location.origin + '/callback', true); 42 | xhr.setRequestHeader('Content-Type', 'application/json'); 43 | xhr.send(JSON.stringify({ 44 | data: editor.get() 45 | })); 46 | } 47 | } 48 | 49 | 50 | function addCloseButton(jsoneditorMenu) { 51 | const closeButton = document.createElement('button') 52 | closeButton.className = 'finish-and-shutdown' 53 | closeButton.title = 'Finish and Shutdown' 54 | jsoneditorMenu.append(closeButton) 55 | closeButton.onclick = () => { 56 | let xhr = new XMLHttpRequest(); 57 | xhr.onreadystatechange = function () { 58 | if (this.readyState == 4 && this.status == 200) { 59 | window.close(); 60 | } 61 | } 62 | xhr.open("GET", window.location.origin + '/close'); 63 | xhr.onerror = () => { 64 | window.close(); 65 | } 66 | xhr.send(null); 67 | } 68 | } 69 | 70 | 71 | function getData() { 72 | var xhr = new XMLHttpRequest(); 73 | xhr.onreadystatechange = function () { 74 | if (this.readyState == 4 && this.status == 200) { 75 | data = JSON.parse(xhr.responseText); 76 | init(data) 77 | } 78 | }; 79 | xhr.open("GET", window.location.origin + '/get_data', true); 80 | xhr.send(); 81 | } 82 | 83 | getData(); 84 | -------------------------------------------------------------------------------- /jsoneditor/files/jsoneditor.min.css: -------------------------------------------------------------------------------- 1 | .jsoneditor input,.jsoneditor input:not([type]),.jsoneditor input[type=search],.jsoneditor input[type=text],.jsoneditor-modal input,.jsoneditor-modal input:not([type]),.jsoneditor-modal input[type=search],.jsoneditor-modal input[type=text]{height:auto;border:inherit;box-shadow:none;font-size:inherit;box-sizing:inherit;padding:inherit;font-family:inherit;transition:none;line-height:inherit}.jsoneditor input:focus,.jsoneditor input:not([type]):focus,.jsoneditor input[type=search]:focus,.jsoneditor input[type=text]:focus,.jsoneditor-modal input:focus,.jsoneditor-modal input:not([type]):focus,.jsoneditor-modal input[type=search]:focus,.jsoneditor-modal input[type=text]:focus{border:inherit;box-shadow:inherit}.jsoneditor textarea,.jsoneditor-modal textarea{height:inherit}.jsoneditor select,.jsoneditor-modal select{display:inherit;height:inherit}.jsoneditor label,.jsoneditor-modal label{font-size:inherit;font-weight:inherit;color:inherit}.jsoneditor table,.jsoneditor-modal table{border-collapse:collapse;width:auto}.jsoneditor td,.jsoneditor th,.jsoneditor-modal td,.jsoneditor-modal th{padding:0;display:table-cell;text-align:left;vertical-align:inherit;border-radius:inherit}.jsoneditor .autocomplete.dropdown{position:absolute;background:#fff;box-shadow:2px 2px 12px rgba(128,128,128,.3);border:1px solid #d3d3d3;overflow-x:hidden;overflow-y:auto;cursor:default;margin:0;padding:5px;text-align:left;outline:0;font-family:consolas,menlo,monaco,"Ubuntu Mono",source-code-pro,monospace;font-size:14px}.jsoneditor .autocomplete.dropdown .item{color:#1a1a1a}.jsoneditor .autocomplete.dropdown .item.hover{background-color:#ebebeb}.jsoneditor .autocomplete.hint{color:#a1a1a1;top:4px;left:4px}.jsoneditor-contextmenu-root{position:relative;width:0;height:0}.jsoneditor-contextmenu{position:absolute;box-sizing:content-box;z-index:2}.jsoneditor-contextmenu .jsoneditor-menu{position:relative;left:0;top:0;width:128px;height:auto;background:#fff;border:1px solid #d3d3d3;box-shadow:2px 2px 12px rgba(128,128,128,.3);list-style:none;margin:0;padding:0}.jsoneditor-contextmenu .jsoneditor-menu button{position:relative;padding:0 8px 0 0;margin:0;width:128px;height:auto;border:none;cursor:pointer;color:#4d4d4d;background:0 0;font-size:14px;font-family:arial,sans-serif;box-sizing:border-box;text-align:left}.jsoneditor-contextmenu .jsoneditor-menu button::-moz-focus-inner{padding:0;border:0}.jsoneditor-contextmenu .jsoneditor-menu button.jsoneditor-default{width:96px}.jsoneditor-contextmenu .jsoneditor-menu button.jsoneditor-expand{float:right;width:32px;height:24px;border-left:1px solid #e5e5e5}.jsoneditor-contextmenu .jsoneditor-menu li{overflow:hidden}.jsoneditor-contextmenu .jsoneditor-menu li ul{display:none;position:relative;left:-10px;top:0;border:none;box-shadow:inset 0 0 10px rgba(128,128,128,.5);padding:0 10px;-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-o-transition:all .3s ease-out;transition:all .3s ease-out}.jsoneditor-contextmenu .jsoneditor-menu li ul .jsoneditor-icon{margin-left:24px}.jsoneditor-contextmenu .jsoneditor-menu li ul li button{padding-left:24px;animation:all ease-in-out 1s}.jsoneditor-contextmenu .jsoneditor-menu li button .jsoneditor-expand{position:absolute;top:0;right:0;width:24px;height:24px;padding:0;margin:0 4px 0 0;background-image:url(./img/jsoneditor-icons.svg);background-position:0 -72px}.jsoneditor-contextmenu .jsoneditor-icon{position:absolute;top:0;left:0;width:24px;height:24px;border:none;padding:0;margin:0;background-image:url(./img/jsoneditor-icons.svg)}.jsoneditor-contextmenu .jsoneditor-text{padding:4px 0 4px 24px;word-wrap:break-word}.jsoneditor-contextmenu .jsoneditor-text.jsoneditor-right-margin{padding-right:24px}.jsoneditor-contextmenu .jsoneditor-separator{height:0;border-top:1px solid #e5e5e5;padding-top:5px;margin-top:5px}.jsoneditor-contextmenu button.jsoneditor-remove .jsoneditor-icon{background-position:-24px 0}.jsoneditor-contextmenu button.jsoneditor-append .jsoneditor-icon{background-position:0 0}.jsoneditor-contextmenu button.jsoneditor-insert .jsoneditor-icon{background-position:0 0}.jsoneditor-contextmenu button.jsoneditor-duplicate .jsoneditor-icon{background-position:-48px 0}.jsoneditor-contextmenu button.jsoneditor-sort-asc .jsoneditor-icon{background-position:-168px 0}.jsoneditor-contextmenu button.jsoneditor-sort-desc .jsoneditor-icon{background-position:-192px 0}.jsoneditor-contextmenu button.jsoneditor-transform .jsoneditor-icon{background-position:-216px 0}.jsoneditor-contextmenu button.jsoneditor-extract .jsoneditor-icon{background-position:0 -24px}.jsoneditor-contextmenu button.jsoneditor-type-string .jsoneditor-icon{background-position:-144px 0}.jsoneditor-contextmenu button.jsoneditor-type-auto .jsoneditor-icon{background-position:-120px 0}.jsoneditor-contextmenu button.jsoneditor-type-object .jsoneditor-icon{background-position:-72px 0}.jsoneditor-contextmenu button.jsoneditor-type-array .jsoneditor-icon{background-position:-96px 0}.jsoneditor-contextmenu button.jsoneditor-type-modes .jsoneditor-icon{background-image:none;width:6px}.jsoneditor-contextmenu li,.jsoneditor-contextmenu ul{box-sizing:content-box;position:relative}.jsoneditor-contextmenu .jsoneditor-menu button:focus,.jsoneditor-contextmenu .jsoneditor-menu button:hover{color:#1a1a1a;background-color:#f5f5f5;outline:0}.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected,.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:focus,.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:hover{color:#fff;background-color:#ee422e}.jsoneditor-contextmenu .jsoneditor-menu li ul li button:focus,.jsoneditor-contextmenu .jsoneditor-menu li ul li button:hover{background-color:#f5f5f5}.jsoneditor-modal{max-width:95%;border-radius:2px!important;padding:45px 15px 15px 15px!important;box-shadow:2px 2px 12px rgba(128,128,128,.3);color:#4d4d4d;line-height:1.3em}.jsoneditor-modal.jsoneditor-modal-transform{width:600px!important}.jsoneditor-modal .pico-modal-header{position:absolute;box-sizing:border-box;top:0;left:0;width:100%;padding:0 10px;height:30px;line-height:30px;font-family:arial,sans-serif;font-size:11pt;background:#3883fa;color:#fff}.jsoneditor-modal table{width:100%}.jsoneditor-modal table td{padding:3px 0}.jsoneditor-modal table td.jsoneditor-modal-input{text-align:right;padding-right:0;white-space:nowrap}.jsoneditor-modal table td.jsoneditor-modal-actions{padding-top:15px}.jsoneditor-modal table th{vertical-align:middle}.jsoneditor-modal p:first-child{margin-top:0}.jsoneditor-modal a{color:#3883fa}.jsoneditor-modal .jsoneditor-jmespath-block{margin-bottom:10px}.jsoneditor-modal .pico-close{background:0 0!important;font-size:24px!important;top:7px!important;right:7px!important;color:#fff}.jsoneditor-modal input{padding:4px}.jsoneditor-modal input[type=text]{cursor:inherit}.jsoneditor-modal input[disabled]{background:#d3d3d3;color:grey}.jsoneditor-modal .jsoneditor-select-wrapper{position:relative;display:inline-block}.jsoneditor-modal .jsoneditor-select-wrapper:after{content:"";width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:6px solid #666;position:absolute;right:8px;top:14px;pointer-events:none}.jsoneditor-modal select{padding:3px 24px 3px 10px;min-width:180px;max-width:350px;-webkit-appearance:none;-moz-appearance:none;appearance:none;text-indent:0;text-overflow:"";font-size:14px;line-height:1.5em}.jsoneditor-modal select::-ms-expand{display:none}.jsoneditor-modal .jsoneditor-button-group input{padding:4px 10px;margin:0;border-radius:0;border-left-style:none}.jsoneditor-modal .jsoneditor-button-group input.jsoneditor-button-first{border-top-left-radius:3px;border-bottom-left-radius:3px;border-left-style:solid}.jsoneditor-modal .jsoneditor-button-group input.jsoneditor-button-last{border-top-right-radius:3px;border-bottom-right-radius:3px}.jsoneditor-modal .jsoneditor-transform-preview{background:#f5f5f5;height:200px}.jsoneditor-modal .jsoneditor-transform-preview.jsoneditor-error{color:#ee422e}.jsoneditor-modal .jsoneditor-jmespath-wizard{line-height:1.2em;width:100%;padding:0;border-radius:3px}.jsoneditor-modal .jsoneditor-jmespath-label{font-weight:700;color:#1e90ff;margin-top:20px;margin-bottom:5px}.jsoneditor-modal .jsoneditor-jmespath-wizard-table{width:100%;border-collapse:collapse}.jsoneditor-modal .jsoneditor-jmespath-wizard-label{font-style:italic;margin:4px 0 2px 0}.jsoneditor-modal .jsoneditor-inline{position:relative;display:inline-block;width:100%;padding-top:2px;padding-bottom:2px}.jsoneditor-modal .jsoneditor-inline:not(:last-child){padding-right:2px}.jsoneditor-modal .jsoneditor-jmespath-filter{display:flex;flex-wrap:wrap}.jsoneditor-modal .jsoneditor-jmespath-filter-field{width:180px}.jsoneditor-modal .jsoneditor-jmespath-filter-relation{width:100px}.jsoneditor-modal .jsoneditor-jmespath-filter-value{min-width:180px;flex:1}.jsoneditor-modal .jsoneditor-jmespath-sort-field{width:170px}.jsoneditor-modal .jsoneditor-jmespath-sort-order{width:150px}.jsoneditor-modal .jsoneditor-jmespath-select-fields{width:100%}.jsoneditor-modal .selectr-selected{border-color:#d3d3d3;padding:4px 28px 4px 8px}.jsoneditor-modal .selectr-selected .selectr-tag{background-color:#3883fa;border-radius:5px}.jsoneditor-modal table td,.jsoneditor-modal table th{text-align:left;vertical-align:middle;font-weight:400;color:#4d4d4d;border-spacing:0;border-collapse:collapse}.jsoneditor-modal #query,.jsoneditor-modal input,.jsoneditor-modal input[type=text],.jsoneditor-modal input[type=text]:focus,.jsoneditor-modal select,.jsoneditor-modal textarea{background:#fff;border:1px solid #d3d3d3;color:#4d4d4d;border-radius:3px;padding:4px}.jsoneditor-modal #query,.jsoneditor-modal textarea{border-radius:unset}.jsoneditor-modal,.jsoneditor-modal #query,.jsoneditor-modal input,.jsoneditor-modal input[type=text],.jsoneditor-modal option,.jsoneditor-modal select,.jsoneditor-modal table td,.jsoneditor-modal table th,.jsoneditor-modal textarea{font-size:10.5pt;font-family:arial,sans-serif}.jsoneditor-modal #query,.jsoneditor-modal .jsoneditor-transform-preview{font-family:consolas,menlo,monaco,"Ubuntu Mono",source-code-pro,monospace;font-size:14px;width:100%;box-sizing:border-box}.jsoneditor-modal input[type=button],.jsoneditor-modal input[type=submit]{background:#f5f5f5;padding:4px 20px}.jsoneditor-modal input,.jsoneditor-modal select{cursor:pointer}.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-asc input.jsoneditor-button-asc,.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-desc input.jsoneditor-button-desc{background:#3883fa;border-color:#3883fa;color:#fff}.jsoneditor{color:#1a1a1a;border:thin solid #3883fa;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;height:100%;position:relative;padding:0;line-height:100%}div.jsoneditor-default,div.jsoneditor-field,div.jsoneditor-readonly,div.jsoneditor-value{border:1px solid transparent;min-height:16px;min-width:32px;line-height:16px;padding:2px;margin:1px;word-wrap:break-word;float:left}div.jsoneditor-field p,div.jsoneditor-value p{margin:0}div.jsoneditor-value{word-break:break-word}div.jsoneditor-value.jsoneditor-empty::after{content:"value"}div.jsoneditor-value.jsoneditor-string{color:#006000}div.jsoneditor-value.jsoneditor-number{color:#ee422e}div.jsoneditor-value.jsoneditor-boolean{color:#ff8c00}div.jsoneditor-value.jsoneditor-null{color:#004ed0}div.jsoneditor-value.jsoneditor-color-value{color:#1a1a1a}div.jsoneditor-value.jsoneditor-invalid{color:#1a1a1a}div.jsoneditor-readonly{min-width:16px;color:grey}div.jsoneditor-empty{border-color:#d3d3d3;border-style:dashed;border-radius:2px}div.jsoneditor-field.jsoneditor-empty::after{content:"field"}div.jsoneditor td{vertical-align:top}div.jsoneditor td.jsoneditor-separator{padding:3px 0;vertical-align:top;color:grey}div.jsoneditor td.jsoneditor-tree{vertical-align:top}div.jsoneditor.busy pre.jsoneditor-preview{background:#f5f5f5;color:grey}div.jsoneditor.busy div.jsoneditor-busy{display:inherit}div.jsoneditor code.jsoneditor-preview{background:0 0}div.jsoneditor.jsoneditor-mode-preview pre.jsoneditor-preview{width:100%;height:100%;box-sizing:border-box;overflow:auto;padding:2px;margin:0;white-space:pre-wrap;word-break:break-all}div.jsoneditor-default{color:grey;padding-left:10px}div.jsoneditor-tree{width:100%;height:100%;position:relative;overflow:auto;background:#fff}div.jsoneditor-tree button.jsoneditor-button{width:24px;height:24px;padding:0;margin:0;border:none;cursor:pointer;background-color:transparent;background-image:url(./img/jsoneditor-icons.svg)}div.jsoneditor-tree button.jsoneditor-button:focus{background-color:#f5f5f5;outline:#e5e5e5 solid 1px}div.jsoneditor-tree button.jsoneditor-collapsed{background-position:0 -48px}div.jsoneditor-tree button.jsoneditor-expanded{background-position:0 -72px}div.jsoneditor-tree button.jsoneditor-contextmenu-button{background-position:-48px -72px}div.jsoneditor-tree button.jsoneditor-invisible{visibility:hidden;background:0 0}div.jsoneditor-tree button.jsoneditor-dragarea{background-image:url(./img/jsoneditor-icons.svg);background-position:-72px -72px;cursor:move}div.jsoneditor-tree :focus{outline:0}div.jsoneditor-tree div.jsoneditor-show-more{display:inline-block;padding:3px 4px;margin:2px 0;background-color:#e5e5e5;border-radius:3px;color:grey;font-family:arial,sans-serif;font-size:14px}div.jsoneditor-tree div.jsoneditor-show-more a{display:inline-block;color:grey}div.jsoneditor-tree div.jsoneditor-color{display:inline-block;width:12px;height:12px;margin:4px;border:1px solid grey;cursor:pointer}div.jsoneditor-tree div.jsoneditor-color.jsoneditor-color-readonly{cursor:inherit}div.jsoneditor-tree div.jsoneditor-date{background:#a1a1a1;color:#fff;font-family:arial,sans-serif;border-radius:3px;display:inline-block;padding:3px;margin:0 3px}div.jsoneditor-tree table.jsoneditor-tree{border-collapse:collapse;border-spacing:0;width:100%}div.jsoneditor-tree .jsoneditor-button{display:block}div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error{width:24px;height:24px;padding:0;margin:0 4px 0 0;background-image:url(./img/jsoneditor-icons.svg);background-position:-168px -48px;background-color:transparent}div.jsoneditor-outer{position:static;width:100%;height:100%;margin:0;padding:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}div.jsoneditor-outer.has-nav-bar{margin-top:-26px;padding-top:26px}div.jsoneditor-outer.has-nav-bar.has-main-menu-bar{margin-top:-61px;padding-top:61px}div.jsoneditor-outer.has-status-bar{margin-bottom:-26px;padding-bottom:26px}div.jsoneditor-outer.has-main-menu-bar{margin-top:-35px;padding-top:35px}div.jsoneditor-busy{position:absolute;top:15%;left:0;box-sizing:border-box;width:100%;text-align:center;display:none}div.jsoneditor-busy span{background-color:#ffffab;border:1px solid #fe0;border-radius:3px;padding:5px 15px;box-shadow:0 0 5px rgba(0,0,0,.4)}div.jsoneditor-field.jsoneditor-empty::after,div.jsoneditor-value.jsoneditor-empty::after{pointer-events:none;color:#d3d3d3;font-size:8pt}a.jsoneditor-value.jsoneditor-url,div.jsoneditor-value.jsoneditor-url{color:#006000;text-decoration:underline}a.jsoneditor-value.jsoneditor-url{display:inline-block;padding:2px;margin:2px}a.jsoneditor-value.jsoneditor-url:focus,a.jsoneditor-value.jsoneditor-url:hover{color:#ee422e}div.jsoneditor-field.jsoneditor-highlight,div.jsoneditor-field[contenteditable=true]:focus,div.jsoneditor-field[contenteditable=true]:hover,div.jsoneditor-value.jsoneditor-highlight,div.jsoneditor-value[contenteditable=true]:focus,div.jsoneditor-value[contenteditable=true]:hover{background-color:#ffffab;border:1px solid #fe0;border-radius:2px}div.jsoneditor-field.jsoneditor-highlight-active,div.jsoneditor-field.jsoneditor-highlight-active:focus,div.jsoneditor-field.jsoneditor-highlight-active:hover,div.jsoneditor-value.jsoneditor-highlight-active,div.jsoneditor-value.jsoneditor-highlight-active:focus,div.jsoneditor-value.jsoneditor-highlight-active:hover{background-color:#fe0;border:1px solid #ffc700;border-radius:2px}div.jsoneditor-value.jsoneditor-array,div.jsoneditor-value.jsoneditor-object{min-width:16px}div.jsoneditor-tree button.jsoneditor-contextmenu-button.jsoneditor-selected,div.jsoneditor-tree button.jsoneditor-contextmenu-button:focus,div.jsoneditor-tree button.jsoneditor-contextmenu-button:hover,tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu-button{background-position:-48px -48px}div.jsoneditor-tree div.jsoneditor-show-more a:focus,div.jsoneditor-tree div.jsoneditor-show-more a:hover{color:#ee422e}.ace-jsoneditor,textarea.jsoneditor-text{min-height:150px}.ace-jsoneditor.ace_editor,textarea.jsoneditor-text.ace_editor{font-family:consolas,menlo,monaco,"Ubuntu Mono",source-code-pro,monospace}textarea.jsoneditor-text{width:100%;height:100%;margin:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;outline-width:0;border:none;background-color:#fff;resize:none}tr.jsoneditor-highlight,tr.jsoneditor-selected{background-color:#d3d3d3}tr.jsoneditor-selected button.jsoneditor-contextmenu-button,tr.jsoneditor-selected button.jsoneditor-dragarea{visibility:hidden}tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu-button,tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea{visibility:visible}div.jsoneditor-tree button.jsoneditor-dragarea:focus,div.jsoneditor-tree button.jsoneditor-dragarea:hover,tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea{background-position:-72px -48px}div.jsoneditor td,div.jsoneditor th,div.jsoneditor tr{padding:0;margin:0}.jsoneditor-popover,.jsoneditor-schema-error,div.jsoneditor td,div.jsoneditor textarea,div.jsoneditor th,div.jsoneditor-field,div.jsoneditor-value,pre.jsoneditor-preview{font-family:consolas,menlo,monaco,"Ubuntu Mono",source-code-pro,monospace;font-size:14px;color:#1a1a1a}.jsoneditor-schema-error{cursor:default;display:inline-block;height:24px;line-height:24px;position:relative;text-align:center;width:24px}.jsoneditor-popover{background-color:#4c4c4c;border-radius:3px;box-shadow:0 0 5px rgba(0,0,0,.4);color:#fff;padding:7px 10px;position:absolute;cursor:auto;width:200px}.jsoneditor-popover.jsoneditor-above{bottom:32px;left:-98px}.jsoneditor-popover.jsoneditor-above:before{border-top:7px solid #4c4c4c;bottom:-7px}.jsoneditor-popover.jsoneditor-below{top:32px;left:-98px}.jsoneditor-popover.jsoneditor-below:before{border-bottom:7px solid #4c4c4c;top:-7px}.jsoneditor-popover.jsoneditor-left{top:-7px;right:32px}.jsoneditor-popover.jsoneditor-left:before{border-left:7px solid #4c4c4c;border-top:7px solid transparent;border-bottom:7px solid transparent;content:"";top:19px;right:-14px;left:inherit;margin-left:inherit;margin-top:-7px;position:absolute}.jsoneditor-popover.jsoneditor-right{top:-7px;left:32px}.jsoneditor-popover.jsoneditor-right:before{border-right:7px solid #4c4c4c;border-top:7px solid transparent;border-bottom:7px solid transparent;content:"";top:19px;left:-14px;margin-left:inherit;margin-top:-7px;position:absolute}.jsoneditor-popover:before{border-right:7px solid transparent;border-left:7px solid transparent;content:"";display:block;left:50%;margin-left:-7px;position:absolute}.jsoneditor-text-errors tr.jump-to-line:hover{text-decoration:underline;cursor:pointer}.jsoneditor-schema-error:focus .jsoneditor-popover,.jsoneditor-schema-error:hover .jsoneditor-popover{display:block;animation:fade-in .3s linear 1,move-up .3s linear 1}@keyframes fade-in{from{opacity:0}to{opacity:1}}.jsoneditor .jsoneditor-validation-errors-container{max-height:130px;overflow-y:auto}.jsoneditor .jsoneditor-validation-errors{width:100%;overflow:hidden}.jsoneditor .jsoneditor-additional-errors{position:absolute;margin:auto;bottom:31px;left:calc(50% - 92px);color:grey;background-color:#ebebeb;padding:7px 15px;border-radius:8px}.jsoneditor .jsoneditor-additional-errors.visible{visibility:visible;opacity:1;transition:opacity 2s linear}.jsoneditor .jsoneditor-additional-errors.hidden{visibility:hidden;opacity:0;transition:visibility 0s 2s,opacity 2s linear}.jsoneditor .jsoneditor-text-errors{width:100%;border-collapse:collapse;border-top:1px solid #ffc700}.jsoneditor .jsoneditor-text-errors td{padding:3px 6px;vertical-align:middle}.jsoneditor .jsoneditor-text-errors td pre{margin:0;white-space:pre-wrap}.jsoneditor .jsoneditor-text-errors tr{background-color:#ffffab}.jsoneditor .jsoneditor-text-errors tr.parse-error{background-color:#ee2e2e70}.jsoneditor-text-errors .jsoneditor-schema-error{border:none;width:24px;height:24px;padding:0;margin:0 4px 0 0;cursor:pointer}.jsoneditor-text-errors tr .jsoneditor-schema-error{background-image:url(./img/jsoneditor-icons.svg);background-position:-168px -48px;background-color:transparent}.jsoneditor-text-errors tr.parse-error .jsoneditor-schema-error{background-image:url(./img/jsoneditor-icons.svg);background-position:-25px 0;background-color:transparent}.jsoneditor-anchor{cursor:pointer}.jsoneditor-anchor .picker_wrapper.popup.popup_bottom{top:28px;left:-10px}.fadein{-webkit-animation:fadein .3s;animation:fadein .3s;-moz-animation:fadein .3s;-o-animation:fadein .3s}@keyframes fadein{0%{opacity:0}100%{opacity:1}}.jsoneditor-modal input[type=search].selectr-input{border:1px solid #d3d3d3;width:calc(100% - 4px);margin:2px;padding:4px;box-sizing:border-box}.jsoneditor-modal button.selectr-input-clear{right:8px}.jsoneditor-menu{width:100%;height:35px;padding:2px;margin:0;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;color:#fff;background-color:#3883fa;border-bottom:1px solid #3883fa}.jsoneditor-menu>.jsoneditor-modes>button,.jsoneditor-menu>button{width:26px;height:26px;margin:2px;padding:0;border-radius:2px;border:1px solid transparent;background-color:transparent;background-image:url(./img/jsoneditor-icons.svg);color:#fff;opacity:.8;font-family:arial,sans-serif;font-size:14px;float:left}.jsoneditor-menu>.jsoneditor-modes>button:hover,.jsoneditor-menu>button:hover{background-color:rgba(255,255,255,.2);border:1px solid rgba(255,255,255,.4)}.jsoneditor-menu>.jsoneditor-modes>button:active,.jsoneditor-menu>.jsoneditor-modes>button:focus,.jsoneditor-menu>button:active,.jsoneditor-menu>button:focus{background-color:rgba(255,255,255,.3)}.jsoneditor-menu>.jsoneditor-modes>button:disabled,.jsoneditor-menu>button:disabled{opacity:.5;background-color:transparent;border:none}.jsoneditor-menu>button.jsoneditor-collapse-all{background-position:0 -96px}.jsoneditor-menu>button.jsoneditor-expand-all{background-position:0 -120px}.jsoneditor-menu>button.jsoneditor-sort{background-position:-120px -96px}.jsoneditor-menu>button.jsoneditor-transform{background-position:-144px -96px}.jsoneditor.jsoneditor-mode-form>.jsoneditor-menu>button.jsoneditor-sort,.jsoneditor.jsoneditor-mode-form>.jsoneditor-menu>button.jsoneditor-transform,.jsoneditor.jsoneditor-mode-view>.jsoneditor-menu>button.jsoneditor-sort,.jsoneditor.jsoneditor-mode-view>.jsoneditor-menu>button.jsoneditor-transform{display:none}.jsoneditor-menu>button.jsoneditor-undo{background-position:-24px -96px}.jsoneditor-menu>button.jsoneditor-undo:disabled{background-position:-24px -120px}.jsoneditor-menu>button.jsoneditor-redo{background-position:-48px -96px}.jsoneditor-menu>button.jsoneditor-redo:disabled{background-position:-48px -120px}.jsoneditor-menu>button.jsoneditor-compact{background-position:-72px -96px}.jsoneditor-menu>button.jsoneditor-format{background-position:-72px -120px}.jsoneditor-menu>button.jsoneditor-repair{background-position:-96px -96px}.jsoneditor-menu>.jsoneditor-modes{display:inline-block;float:left}.jsoneditor-menu>.jsoneditor-modes>button{background-image:none;width:auto;padding-left:6px;padding-right:6px}.jsoneditor-menu>.jsoneditor-modes>button.jsoneditor-separator,.jsoneditor-menu>button.jsoneditor-separator{margin-left:10px}.jsoneditor-menu a{font-family:arial,sans-serif;font-size:14px;color:#fff;opacity:.8;vertical-align:middle}.jsoneditor-menu a:hover{opacity:1}.jsoneditor-menu a.jsoneditor-poweredBy{font-size:8pt;position:absolute;right:0;top:0;padding:10px}.jsoneditor-navigation-bar{width:100%;height:26px;line-height:26px;padding:0;margin:0;border-bottom:1px solid #d3d3d3;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;color:grey;background-color:#ebebeb;overflow:hidden;font-family:arial,sans-serif;font-size:14px}.jsoneditor-search{font-family:arial,sans-serif;position:absolute;right:4px;top:4px;border-collapse:collapse;border-spacing:0;display:flex}.jsoneditor-search input{color:#1a1a1a;width:120px;border:none;outline:0;margin:1px;line-height:20px;font-family:arial,sans-serif}.jsoneditor-search button{width:16px;height:24px;padding:0;margin:0;border:none;background:url(./img/jsoneditor-icons.svg);vertical-align:top}.jsoneditor-search button:hover{background-color:transparent}.jsoneditor-search button.jsoneditor-refresh{width:18px;background-position:-99px -73px}.jsoneditor-search button.jsoneditor-next{cursor:pointer;background-position:-124px -73px}.jsoneditor-search button.jsoneditor-next:hover{background-position:-124px -49px}.jsoneditor-search button.jsoneditor-previous{cursor:pointer;background-position:-148px -73px;margin-right:2px}.jsoneditor-search button.jsoneditor-previous:hover{background-position:-148px -49px}.jsoneditor-results{font-family:arial,sans-serif;color:#fff;padding-right:5px;line-height:26px}.jsoneditor-frame{border:1px solid transparent;background-color:#fff;padding:0 2px;margin:0}.jsoneditor-statusbar{line-height:26px;height:26px;color:grey;background-color:#ebebeb;border-top:1px solid #d3d3d3;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px}.jsoneditor-statusbar>.jsoneditor-curserinfo-val{margin-right:12px}.jsoneditor-statusbar>.jsoneditor-curserinfo-count{margin-left:4px}.jsoneditor-statusbar>.jsoneditor-validation-error-icon{float:right;width:24px;height:24px;padding:0;margin-top:1px;background-image:url(./img/jsoneditor-icons.svg);background-position:-168px -48px;cursor:pointer}.jsoneditor-statusbar>.jsoneditor-validation-error-count{float:right;margin:0 4px 0 0;cursor:pointer}.jsoneditor-statusbar>.jsoneditor-parse-error-icon{float:right;width:24px;height:24px;padding:0;margin:1px;background-image:url(./img/jsoneditor-icons.svg);background-position:-25px 0}.jsoneditor-statusbar .jsoneditor-array-info a{color:inherit}div.jsoneditor-statusbar>.jsoneditor-curserinfo-label,div.jsoneditor-statusbar>.jsoneditor-size-info{margin:0 4px}.jsoneditor-treepath{padding:0 5px;overflow:hidden;white-space:nowrap;outline:0}.jsoneditor-treepath.show-all{word-wrap:break-word;white-space:normal;position:absolute;background-color:#ebebeb;z-index:1;box-shadow:2px 2px 12px rgba(128,128,128,.3)}.jsoneditor-treepath.show-all span.jsoneditor-treepath-show-all-btn{display:none}.jsoneditor-treepath div.jsoneditor-contextmenu-root{position:absolute;left:0}.jsoneditor-treepath .jsoneditor-treepath-show-all-btn{position:absolute;background-color:#ebebeb;left:0;height:20px;padding:0 3px;cursor:pointer}.jsoneditor-treepath .jsoneditor-treepath-element{margin:1px;font-family:arial,sans-serif;font-size:14px}.jsoneditor-treepath .jsoneditor-treepath-seperator{margin:2px;font-size:9pt;font-family:arial,sans-serif}.jsoneditor-treepath span.jsoneditor-treepath-element:hover,.jsoneditor-treepath span.jsoneditor-treepath-seperator:hover{cursor:pointer;text-decoration:underline}/*! 2 | * Selectr 2.4.0 3 | * https://github.com/Mobius1/Selectr 4 | * 5 | * Released under the MIT license 6 | */.selectr-container{position:relative}.selectr-container li{list-style:none}.selectr-hidden{position:absolute;overflow:hidden;clip:rect(0,0,0,0);width:1px;height:1px;margin:-1px;padding:0;border:0 none}.selectr-visible{position:absolute;left:0;top:0;width:100%;height:100%;opacity:0;z-index:11}.selectr-desktop.multiple .selectr-visible{display:none}.selectr-desktop.multiple.native-open .selectr-visible{top:100%;min-height:200px!important;height:auto;opacity:1;display:block}.selectr-container.multiple.selectr-mobile .selectr-selected{z-index:0}.selectr-selected{position:relative;z-index:1;box-sizing:border-box;width:100%;padding:7px 28px 7px 14px;cursor:pointer;border:1px solid #999;border-radius:3px;background-color:#fff}.selectr-selected::before{position:absolute;top:50%;right:10px;width:0;height:0;content:"";-o-transform:rotate(0) translate3d(0,-50%,0);-ms-transform:rotate(0) translate3d(0,-50%,0);-moz-transform:rotate(0) translate3d(0,-50%,0);-webkit-transform:rotate(0) translate3d(0,-50%,0);transform:rotate(0) translate3d(0,-50%,0);border-width:4px 4px 0 4px;border-style:solid;border-color:#6c7a86 transparent transparent}.selectr-container.native-open .selectr-selected::before,.selectr-container.open .selectr-selected::before{border-width:0 4px 4px 4px;border-style:solid;border-color:transparent transparent #6c7a86}.selectr-label{display:none;overflow:hidden;width:100%;white-space:nowrap;text-overflow:ellipsis}.selectr-placeholder{color:#6c7a86}.selectr-tags{margin:0;padding:0;white-space:normal}.has-selected .selectr-tags{margin:0 0 -2px}.selectr-tag{list-style:none;position:relative;float:left;padding:2px 25px 2px 8px;margin:0 2px 2px 0;cursor:default;color:#fff;border:medium none;border-radius:10px;background:#acb7bf none repeat scroll 0 0}.selectr-container.multiple.has-selected .selectr-selected{padding:5px 28px 5px 5px}.selectr-options-container{position:absolute;z-index:10000;top:calc(100% - 1px);left:0;display:none;box-sizing:border-box;width:100%;border-width:0 1px 1px;border-style:solid;border-color:transparent #999 #999;border-radius:0 0 3px 3px;background-color:#fff}.selectr-container.open .selectr-options-container{display:block}.selectr-input-container{position:relative;display:none}.selectr-clear,.selectr-input-clear,.selectr-tag-remove{position:absolute;top:50%;right:22px;width:20px;height:20px;padding:0;cursor:pointer;-o-transform:translate3d(0,-50%,0);-ms-transform:translate3d(0,-50%,0);-moz-transform:translate3d(0,-50%,0);-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);border:medium none;background-color:transparent;z-index:11}.selectr-clear,.selectr-input-clear{display:none}.selectr-container.has-selected .selectr-clear,.selectr-input-container.active .selectr-input-clear{display:block}.selectr-selected .selectr-tag-remove{right:2px}.selectr-clear::after,.selectr-clear::before,.selectr-input-clear::after,.selectr-input-clear::before,.selectr-tag-remove::after,.selectr-tag-remove::before{position:absolute;top:5px;left:9px;width:2px;height:10px;content:" ";background-color:#6c7a86}.selectr-tag-remove::after,.selectr-tag-remove::before{top:4px;width:3px;height:12px;background-color:#fff}.selectr-clear:before,.selectr-input-clear::before,.selectr-tag-remove::before{-o-transform:rotate(45deg);-ms-transform:rotate(45deg);-moz-transform:rotate(45deg);-webkit-transform:rotate(45deg);transform:rotate(45deg)}.selectr-clear:after,.selectr-input-clear::after,.selectr-tag-remove::after{-o-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.selectr-input-container.active,.selectr-input-container.active .selectr-clear{display:block}.selectr-input{top:5px;left:5px;box-sizing:border-box;width:calc(100% - 30px);margin:10px 15px;padding:7px 30px 7px 9px;border:1px solid #999;border-radius:3px}.selectr-notice{display:none;box-sizing:border-box;width:100%;padding:8px 16px;border-top:1px solid #999;border-radius:0 0 3px 3px;background-color:#fff}.selectr-container.notice .selectr-notice{display:block}.selectr-container.notice .selectr-selected{border-radius:3px 3px 0 0}.selectr-options{position:relative;top:calc(100% + 2px);display:none;overflow-x:auto;overflow-y:scroll;max-height:200px;margin:0;padding:0}.selectr-container.notice .selectr-options-container,.selectr-container.open .selectr-input-container,.selectr-container.open .selectr-options{display:block}.selectr-option{position:relative;display:block;padding:5px 20px;list-style:outside none none;cursor:pointer;font-weight:400}.selectr-options.optgroups>.selectr-option{padding-left:25px}.selectr-optgroup{font-weight:700;padding:0}.selectr-optgroup--label{font-weight:700;margin-top:10px;padding:5px 15px}.selectr-match{text-decoration:underline}.selectr-option.selected{background-color:#ddd}.selectr-option.active{color:#fff;background-color:#5897fb}.selectr-option.disabled{opacity:.4}.selectr-option.excluded{display:none}.selectr-container.open .selectr-selected{border-color:#999 #999 transparent #999;border-radius:3px 3px 0 0}.selectr-container.open .selectr-selected::after{-o-transform:rotate(180deg) translate3d(0,50%,0);-ms-transform:rotate(180deg) translate3d(0,50%,0);-moz-transform:rotate(180deg) translate3d(0,50%,0);-webkit-transform:rotate(180deg) translate3d(0,50%,0);transform:rotate(180deg) translate3d(0,50%,0)}.selectr-disabled{opacity:.6}.has-selected .selectr-placeholder,.selectr-empty{display:none}.has-selected .selectr-label{display:block}.taggable .selectr-selected{padding:4px 28px 4px 4px}.taggable .selectr-selected::after{display:table;content:" ";clear:both}.taggable .selectr-label{width:auto}.taggable .selectr-tags{float:left;display:block}.taggable .selectr-placeholder{display:none}.input-tag{float:left;min-width:90px;width:auto}.selectr-tag-input{border:medium none;padding:3px 10px;width:100%;font-family:inherit;font-weight:inherit;font-size:inherit}.selectr-input-container.loading::after{position:absolute;top:50%;right:20px;width:20px;height:20px;content:"";-o-transform:translate3d(0,-50%,0);-ms-transform:translate3d(0,-50%,0);-moz-transform:translate3d(0,-50%,0);-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);-o-transform-origin:50% 0 0;-ms-transform-origin:50% 0 0;-moz-transform-origin:50% 0 0;-webkit-transform-origin:50% 0 0;transform-origin:50% 0 0;-moz-animation:.5s linear 0s normal forwards infinite running selectr-spin;-webkit-animation:.5s linear 0s normal forwards infinite running selectr-spin;animation:.5s linear 0s normal forwards infinite running selectr-spin;border-width:3px;border-style:solid;border-color:#aaa #ddd #ddd;border-radius:50%}@-webkit-keyframes selectr-spin{0%{-webkit-transform:rotate(0) translate3d(0,-50%,0);transform:rotate(0) translate3d(0,-50%,0)}100%{-webkit-transform:rotate(360deg) translate3d(0,-50%,0);transform:rotate(360deg) translate3d(0,-50%,0)}}@keyframes selectr-spin{0%{-webkit-transform:rotate(0) translate3d(0,-50%,0);transform:rotate(0) translate3d(0,-50%,0)}100%{-webkit-transform:rotate(360deg) translate3d(0,-50%,0);transform:rotate(360deg) translate3d(0,-50%,0)}}.selectr-container.open.inverted .selectr-selected{border-color:transparent #999 #999;border-radius:0 0 3px 3px}.selectr-container.inverted .selectr-options-container{border-width:1px 1px 0;border-color:#999 #999 transparent;border-radius:3px 3px 0 0;background-color:#fff}.selectr-container.inverted .selectr-options-container{top:auto;bottom:calc(100% - 1px)}.selectr-container ::-webkit-input-placeholder{color:#6c7a86;opacity:1}.selectr-container ::-moz-placeholder{color:#6c7a86;opacity:1}.selectr-container :-ms-input-placeholder{color:#6c7a86;opacity:1}.selectr-container ::placeholder{color:#6c7a86;opacity:1} -------------------------------------------------------------------------------- /jsoneditor/jsoneditor.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ast 3 | import csv 4 | import json 5 | import mimetypes 6 | import yaml 7 | import os 8 | import random 9 | import subprocess 10 | import sys 11 | import threading 12 | import webbrowser 13 | from collections.abc import Mapping 14 | from io import TextIOWrapper 15 | from typing import Any, Callable, Union 16 | from urllib.parse import urlparse 17 | from wsgiref.simple_server import WSGIRequestHandler, make_server 18 | 19 | import pyperclip 20 | import requests 21 | 22 | # Get installation dir 23 | INSTALL_DIR = os.path.dirname(os.path.realpath(__file__)) 24 | RESOURCES = [ 25 | "/", 26 | "/files/jsoneditor.min.css", 27 | "/files/index.css", 28 | "/files/jsoneditor.min.js", 29 | "/files/index.js", 30 | "/get_data", 31 | "/files/img/jsoneditor-icons.svg", 32 | "/files/img/favicon.ico", 33 | ] 34 | 35 | 36 | class AltWsgiHandler(WSGIRequestHandler): 37 | def log_message(self, _format, *args) -> None: 38 | if self.path in self.server.resources: 39 | self.server.resources.remove(self.path) 40 | if self.path == "/close" or ( 41 | not self.server.keep_running and not self.server.resources 42 | ): 43 | self.server.stop() 44 | 45 | 46 | class Server: 47 | def __init__( 48 | self, 49 | data: Union[dict, str, list], 50 | callback: Callable[[dict], Any] = None, 51 | options: dict = None, 52 | additional_js: str = None, 53 | keep_running: bool = False, 54 | run_in_thread: bool = False, 55 | is_csv: bool = False, 56 | is_yaml: bool = False, 57 | is_ndjson: bool = False, 58 | is_js_object: bool = False, 59 | title: str = None, 60 | port: int = None, 61 | no_browser: bool = False, 62 | ) -> None: 63 | self.callback = callback 64 | self.options = options 65 | self.additional_js = additional_js 66 | self.keep_running = keep_running 67 | self.run_in_thread = run_in_thread 68 | self.is_csv = is_csv 69 | self.is_yaml = is_yaml 70 | self.is_ndjson = is_ndjson 71 | self.is_js_object = is_js_object 72 | self.title = title 73 | self.no_browser = no_browser 74 | self.get_random_port(port) 75 | self.data = self.get_json(data) 76 | self.server = None 77 | 78 | def get_random_port(self, port: int = None): 79 | self.port = port or random.randint(1023, 65353) 80 | 81 | def send_response( 82 | self, status: str, content_type: str, respond: Callable, cache: bool = False 83 | ): 84 | headers = [("Content-type", content_type)] 85 | if cache: 86 | headers.append(("Cache-Control", "max-age=259200")) 87 | respond(status, headers) 88 | 89 | def get_json(self, source: Union[dict, str, list]) -> dict: 90 | # Get json data 91 | retrieved_data = None 92 | if type(source) is str: 93 | if self.is_url(source): 94 | self.detect_source_by_filename(source) 95 | self.title = self.title or source.split("/")[-1].split("?")[0] 96 | retrieved_data = requests.get(source).text 97 | elif self.is_file(source): 98 | self.detect_source_by_filename(source) 99 | self.title = self.title or os.path.basename(source) 100 | retrieved_data = open(source, "r", encoding="utf-8") 101 | else: 102 | retrieved_data = source 103 | try: 104 | data = self.load_json(retrieved_data) 105 | except Exception as e: 106 | # We were unable to understand the data, so we print 107 | # it so the user can understand what happened 108 | print("Input:\n" + source) 109 | raise e 110 | 111 | elif isinstance(source, Mapping): 112 | # Convert to dict as some mappings are not json serializable 113 | data = dict(source) 114 | else: 115 | # Source is a list 116 | data = source 117 | return data 118 | 119 | @staticmethod 120 | def is_url(source: str) -> bool: 121 | try: 122 | result = urlparse(source) 123 | is_url = all([result.scheme, result.netloc]) 124 | except: 125 | is_url = False 126 | return is_url 127 | 128 | @staticmethod 129 | def is_file(source: str) -> bool: 130 | return os.path.exists(source) 131 | 132 | def detect_source_by_filename(self, source: str): 133 | if source.endswith(".csv"): 134 | self.is_csv = True 135 | elif source.endswith(".yaml"): 136 | self.is_yaml = True 137 | elif any(source.endswith(ext) for ext in [".ndjson", ".jsonl"]): 138 | self.is_ndjson = True 139 | 140 | def load_json(self, source): 141 | if self.is_csv: 142 | if isinstance(source, str): 143 | # Throws an error if the input is not valid csv 144 | csv.Sniffer().sniff(source[:1024], delimiters=",:;\t") 145 | result = list(csv.DictReader(source.split("\n"))) 146 | elif isinstance(source, TextIOWrapper): 147 | result = list(csv.DictReader(source)) 148 | elif self.is_yaml: 149 | result = list(filter(bool, yaml.load_all(source, Loader=yaml.UnsafeLoader))) 150 | if len(result) == 1: 151 | result = result[0] 152 | elif self.is_ndjson: 153 | if isinstance(source, str): 154 | lines = source.splitlines() 155 | elif isinstance(source, TextIOWrapper): 156 | lines = source.readlines() 157 | result = list(json.loads(line) for line in lines) 158 | elif self.is_js_object: 159 | try: 160 | node = subprocess.Popen(['node'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) 161 | stdout, _ = node.communicate(f"console.log(JSON.stringify({source}))".encode()) 162 | result = json.loads(stdout.decode()) 163 | except FileNotFoundError: 164 | print( 165 | "You need to have Nodejs installed to be able to use JavaScript Objects." 166 | ) 167 | sys.exit(1) 168 | else: 169 | if isinstance(source, str): 170 | try: 171 | result = json.loads(source) 172 | except ValueError: 173 | result = ast.literal_eval(source) 174 | elif isinstance(source, TextIOWrapper): 175 | result = json.load(source) 176 | if isinstance(source, TextIOWrapper): 177 | source.close() 178 | return result 179 | 180 | def wsgi_app(self, environ, respond): 181 | path = environ["PATH_INFO"] 182 | method = environ["REQUEST_METHOD"] 183 | file_path = INSTALL_DIR + path 184 | # index.html 185 | if method == "GET": 186 | if path == "/": 187 | self.send_response("200 OK", "text/html", respond) 188 | with open(INSTALL_DIR + "/files/index.html", "rb") as f: 189 | content = f.read() 190 | yield content 191 | # Data endpiont 192 | elif path == "/get_data": 193 | self.send_response("200 OK", "application/json", respond) 194 | data = { 195 | "data": self.data, 196 | "callback": bool(self.callback), 197 | "options": self.options, 198 | "additional_js": self.additional_js, 199 | "title": self.title or "jsoneditor", 200 | "keep_running": self.keep_running, 201 | } 202 | yield json.dumps(data, default=repr).encode("utf-8") 203 | # Close endpoint 204 | elif path == "/close": 205 | self.send_response("200 OK", "text/plain", respond) 206 | yield b"" 207 | # Serve static files 208 | elif path.startswith("/files") and os.path.exists(file_path): 209 | mimetype = mimetypes.guess_type(file_path)[0] 210 | self.send_response("200 OK", mimetype, respond, True) 211 | with open(file_path, "rb") as f: 212 | content = f.read() 213 | yield content 214 | # 404 215 | else: 216 | self.send_response("404 Not Found", "text/plain", respond) 217 | yield b"" 218 | # callback endpoint 219 | elif method == "POST": 220 | if path == "/callback": 221 | request_body_size = int(environ["CONTENT_LENGTH"]) 222 | callback_data = json.loads( 223 | environ["wsgi.input"].read(request_body_size).decode("utf-8") 224 | )["data"] 225 | # fix for edit mode with keep alive setting 226 | # apply the recieved data to instance data 227 | # without this a refresh doesn't reflect saved changes 228 | self.data = callback_data 229 | self.callback(callback_data) 230 | self.send_response("200 OK", "text/plain", respond) 231 | yield b"" 232 | 233 | def start(self): 234 | # We might get an error if the port is in use. 235 | while True: 236 | try: 237 | self.server = make_server( 238 | "", self.port, self.wsgi_app, handler_class=AltWsgiHandler 239 | ) 240 | break 241 | except OSError: 242 | self.get_random_port() 243 | 244 | self.server.resources = RESOURCES.copy() 245 | 246 | self.server.keep_running = self.keep_running 247 | self.server.stop = self.stop 248 | 249 | if not self.run_in_thread: 250 | open_browser(self.port, self.no_browser) 251 | 252 | self.server.serve_forever() 253 | 254 | def stop(self): 255 | if self.server: 256 | self.server._BaseServer__shutdown_request = True 257 | self.server.server_close() 258 | 259 | 260 | # Entry point 261 | def editjson( 262 | data: Union[dict, str, list], 263 | callback: Callable[[dict], Any] = None, 264 | options: dict = None, 265 | additional_js: str = None, 266 | keep_running: bool = False, 267 | run_in_thread: bool = False, 268 | is_csv: bool = False, 269 | is_yaml: bool = False, 270 | is_ndjson: bool = False, 271 | is_js_object: bool = False, 272 | title: str = None, 273 | port: int = None, 274 | no_browser: bool = False, 275 | ) -> Server: 276 | keep_running = keep_running or bool(callback) 277 | 278 | server = Server( 279 | data, 280 | callback, 281 | options, 282 | additional_js, 283 | keep_running, 284 | run_in_thread, 285 | is_csv, 286 | is_yaml, 287 | is_ndjson, 288 | is_js_object, 289 | title, 290 | port, 291 | no_browser, 292 | ) 293 | 294 | if server.run_in_thread: 295 | thread = threading.Thread(target=server.start) 296 | thread.start() 297 | open_browser(server.port, no_browser) 298 | else: 299 | server.start() 300 | 301 | return server 302 | 303 | 304 | def open_browser(port: int, no_browser: bool) -> None: 305 | if no_browser: 306 | print(f"Please open this link to see the JSON: http://localhost:{port}/") 307 | else: 308 | browser_opened = webbrowser.open(f"http://localhost:{port}/") 309 | if not browser_opened: 310 | print( 311 | f"Couldn't launch browser, Please open this link to see the JSON: http://localhost:{port}/" 312 | ) 313 | 314 | 315 | # cli function 316 | def main() -> None: 317 | from . import __version__ 318 | 319 | parser = argparse.ArgumentParser( 320 | description=("View and edit your JSON data in the browser.") 321 | ) 322 | parser.add_argument( 323 | "--version", 324 | action="version", 325 | version="%(prog)s {version}".format(version=__version__), 326 | ) 327 | parser.add_argument( 328 | "data", 329 | help="The data, can be the raw data or a url that will return the data or a file path", 330 | nargs="?", 331 | ) 332 | parser.add_argument( 333 | "-o", 334 | help="Add a button that will output the JSON back to the console", 335 | action="store_true", 336 | ) 337 | parser.add_argument("-b", help="Keep running in background", action="store_true") 338 | parser.add_argument("-c", help="Get data input from clipboard", action="store_true") 339 | parser.add_argument("-k", help="Keep alive", action="store_true") 340 | parser.add_argument("-e", help="Edit mode", action="store_true") 341 | parser.add_argument("-n", help="Don't launch browser", action="store_true") 342 | parser.add_argument("-p", help="Server port") 343 | parser.add_argument("--out", help="File to output when in edit mode") 344 | parser.add_argument("-t", help="Title to display in browser window") 345 | parser.add_argument("--csv", help="Input is CSV", action="store_true") 346 | parser.add_argument("--yaml", help="Input is YAML", action="store_true") 347 | parser.add_argument( 348 | "--js", help="Input is a JavaScript Object", action="store_true" 349 | ) 350 | parser.add_argument( 351 | "--ndjson", help="Input is Newline Delimited JSON", action="store_true" 352 | ) 353 | args = parser.parse_args() 354 | 355 | options = {} 356 | if args.o: 357 | options["callback"] = lambda data: print(json.dumps(data)) 358 | 359 | if args.b: 360 | options["run_in_background"] = True 361 | 362 | if args.k: 363 | options["keep_running"] = True 364 | 365 | if args.p: 366 | options["port"] = int(args.p) 367 | 368 | if args.t: 369 | options["title"] = args.t 370 | 371 | if args.n: 372 | options["no_browser"] = True 373 | 374 | if args.csv: 375 | options["is_csv"] = True 376 | 377 | if args.yaml: 378 | options["is_yaml"] = True 379 | 380 | if args.js: 381 | options["is_js_object"] = True 382 | 383 | if args.ndjson: 384 | options["is_ndjson"] = True 385 | 386 | if not os.isatty(0): 387 | options["data"] = "".join(x for x in sys.stdin) 388 | else: 389 | if args.data: 390 | options["data"] = args.data 391 | elif args.c: 392 | options["data"] = pyperclip.paste() 393 | else: 394 | raise ValueError("No data passed.") 395 | 396 | if args.e: 397 | 398 | def edit_file(json_data: dict): 399 | file_path = None 400 | if args.out: 401 | file_path = args.out 402 | elif os.path.exists(options["data"]): 403 | file_path = options["data"] 404 | if file_path: 405 | with open(file_path, "w", encoding="utf-8") as f: 406 | if args.csv or options["data"].endswith(".csv"): 407 | csv_fields = json_data[0].keys() if json_data else [] 408 | writer = csv.DictWriter(f, csv_fields) 409 | writer.writeheader() 410 | writer.writerows(json_data) 411 | else: 412 | json.dump(json_data, f) 413 | else: 414 | raise ValueError( 415 | "You have not specified a --out path, I don't know where to save." 416 | ) 417 | 418 | options["callback"] = edit_file 419 | 420 | editjson(**options) 421 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pyperclip 3 | pyyaml 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import re 3 | 4 | from setuptools import setup 5 | 6 | 7 | HERE = pathlib.Path(__file__).parent 8 | 9 | 10 | with open(HERE / "jsoneditor/__init__.py", encoding="utf-8") as f: 11 | version = re.findall(r"__version__ = \"(.+)\"", f.read())[0] 12 | 13 | 14 | with open(HERE / "README.md", encoding="utf-8") as f: 15 | readme = f.read() 16 | 17 | with open(HERE / "requirements.txt", encoding="utf-8") as f: 18 | requirements = [r.strip() for r in f] 19 | 20 | setup( 21 | name="jsoneditor", 22 | version=version, 23 | packages=["jsoneditor"], 24 | include_package_data=True, 25 | url="https://github.com/dermasmid/py-jsoneditor", 26 | license="MIT", 27 | long_description=readme, 28 | long_description_content_type="text/markdown", 29 | author="Cheskel Twersky", 30 | author_email="twerskycheskel@gmail.com", 31 | description="Visualize and edit JSON", 32 | keywords="python3 json jsoneditor api gui editor csv", 33 | classifiers=[ 34 | "Programming Language :: Python :: 3", 35 | "License :: OSI Approved :: MIT License", 36 | "Operating System :: OS Independent", 37 | ], 38 | install_requires=requirements, 39 | python_requires=">=3.6", 40 | entry_points={"console_scripts": ["jsoneditor = jsoneditor:main"]}, 41 | ) 42 | -------------------------------------------------------------------------------- /tests/test.csv: -------------------------------------------------------------------------------- 1 | Numeric,Numeric-2,Numeric-Suffix 2 | 1,01,1st 3 | 2,02,2nd 4 | 3,03,3rd 5 | 4,04,4th 6 | 5,05,5th 7 | 6,06,6th 8 | 7,07,7th 9 | 8,08,8th 10 | 9,09,9th 11 | 10,10,10th 12 | 11,11,11th 13 | 12,12,12th 14 | 13,13,13th 15 | 14,14,14th 16 | 15,15,15th 17 | 16,16,16th 18 | 17,17,17th 19 | 18,18,18th 20 | 19,19,19th 21 | 20,20,20th 22 | 21,21,21st 23 | 22,22,22nd 24 | 23,23,23rd 25 | 24,24,24th 26 | 25,25,25th 27 | 26,26,26th 28 | 27,27,27th 29 | 28,28,28th 30 | 29,29,29th 31 | 30,30,30th 32 | 31,31,31st -------------------------------------------------------------------------------- /tests/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "GlossDiv": { 5 | "title": "S", 6 | "GlossList": { 7 | "GlossEntry": { 8 | "ID": "SGML", 9 | "SortAs": "SGML", 10 | "GlossTerm": "Standard Generalized Markup Language", 11 | "Acronym": "SGML", 12 | "Abbrev": "ISO 8879:1986", 13 | "GlossDef": { 14 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 15 | "GlossSeeAlso": ["GML", "XML"] 16 | }, 17 | "GlossSee": "markup" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import platform 4 | 5 | if platform.system() == 'Windows': 6 | separator = '\\' 7 | else: 8 | separator = '/' 9 | 10 | sys.path.insert(0, '/'.join(os.path.dirname(os.path.realpath(__file__)).split(separator)[:-1]) + '/jsoneditor') 11 | 12 | import jsoneditor 13 | import requests 14 | 15 | 16 | 17 | 18 | # Test dict 19 | jsoneditor.editjson(requests.get('https://jsonplaceholder.typicode.com/posts').json()) 20 | 21 | 22 | # Test string 23 | jsoneditor.editjson(requests.get('https://jsonplaceholder.typicode.com/comments').text) 24 | 25 | 26 | # Test editing 27 | jsoneditor.editjson({'hi': '#466'}, print, {'colorPicker': True}, run_in_thread= True) 28 | 29 | 30 | # Test urls 31 | jsoneditor.editjson('https://jsonplaceholder.typicode.com/users') 32 | 33 | # Test csv 34 | jsoneditor.editjson('test.csv', is_csv=True) 35 | --------------------------------------------------------------------------------