├── .ert-runner ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .travis.yml ├── Cask ├── README.md ├── images ├── json-reformat-2-after.png ├── json-reformat-2-before.png └── json-reformat_demo.gif ├── json-reformat.el └── test ├── json-reformat-test.el └── test-helper.el /.ert-runner: -------------------------------------------------------------------------------- 1 | -L . 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - uses: actions/setup-python@v2 9 | - uses: purcell/setup-emacs@master 10 | with: 11 | version: '24.3' 12 | - uses: conao3/setup-cask@master 13 | 14 | - name: Run tests 15 | run: | 16 | cask install 17 | cask exec ert-runner 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | .cask 3 | elpa 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: emacs-lisp 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | matrix: 8 | allow_failures: 9 | - env: EMACS=emacs-snapshot EMACS_PPA=ppa:ubuntu-elisp/ppa 10 | 11 | env: 12 | matrix: 13 | - EMACS=emacs24 EMACS_PPA=ppa:cassou/emacs 14 | - EMACS=emacs-snapshot EMACS_PPA=ppa:ubuntu-elisp/ppa 15 | global: 16 | - PATH=$HOME/.cask/bin:$PATH 17 | 18 | before_install: 19 | - sudo add-apt-repository -y "$EMACS_PPA" 20 | - sudo apt-get update -qq 21 | - sudo apt-get install --force-yes -qq "$EMACS" 22 | - sudo apt-get install --force-yes -qq "${EMACS}-el" || true # OK to fail 23 | - curl -fsSkL --max-time 10 --retry 10 --retry-delay 10 https://raw.github.com/cask/cask/master/go | python 24 | - cask install 25 | script: 26 | cask exec ert-runner 27 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source melpa) 2 | (source gnu) 3 | 4 | (package-file "json-reformat.el") 5 | 6 | (development 7 | (depends-on "ert-runner") 8 | (depends-on "undercover")) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSON Reformat 2 | ============= 3 | 4 | [](https://travis-ci.org/gongo/json-reformat) 5 |  6 | [](https://coveralls.io/r/gongo/json-reformat?branch=master) 7 | [![melpa badge][melpa-badge]][melpa-link] 8 | [![melpa stable badge][melpa-stable-badge]][melpa-stable-link] 9 | 10 | `json-reformat.el` is reformat tool for [JSON](http://en.wikipedia.org/wiki/JavaScript_Object_Notation). 11 | 12 | ## Important 13 | 14 | From **emacs 24.4** , `json-pretty-print` and `json-pretty-print-buffer` (similar specifications as `json-reformat-region`) was bundled. 15 | 16 | ## Requirements 17 | 18 | - Emacs 23 or higher 19 | 20 | ## Installation 21 | 22 | You can install from [MELPA](http://melpa.milkbox.net/) with package.el 23 | 24 | M-x package-install RET json-reformat 25 | 26 | ## Usage 27 | 28 | ``` 29 | M-x json-reformat-region 30 | ``` 31 | 32 | ### Sample 1 33 | 34 |  35 | 36 | ### Sample 2 37 | 38 |  39 | 40 |  41 | 42 | ## Configuration 43 | 44 | ```lisp 45 | json-reformat:indent-width (integer) 46 | 47 | Change indentation level (default 4) 48 | 49 | json-reformat:pretty-string? (boolean) 50 | 51 | Specify whether to decode the string (default nil) 52 | 53 | Example: 54 | 55 | ;; {"name":"foo\"bar","nick":"foo \u00e4 bar","description":"
\nbaz\n","home":"/home/foobar"} 56 | 57 | If nil: 58 | 59 | { 60 | "name": "foo\"bar", 61 | "nick": "foo \u00e4 bar", 62 | "description": "
\nbaz\n<\/pre>", 63 | "home": "\/home\/foobar" 64 | } 65 | 66 | Else t: 67 | 68 | { 69 | "name": "foo\"bar", 70 | "nick": "foo ä bar", 71 | "description": "72 | baz 73 |", 74 | "home": "/home/foobar" 75 | } 76 | ``` 77 | 78 | ## LICENSE 79 | 80 | MIT License. see `json-reformat.el` 81 | 82 | [melpa-link]: http://melpa.org/#/json-reformat 83 | [melpa-stable-link]: http://stable.melpa.org/#/json-reformat 84 | [melpa-badge]: http://melpa.org/packages/json-reformat-badge.svg 85 | [melpa-stable-badge]: http://stable.melpa.org/packages/json-reformat-badge.svg 86 | -------------------------------------------------------------------------------- /images/json-reformat-2-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongo/json-reformat/9120ab67c5379c44bc7a7a07ca858670cea4f32f/images/json-reformat-2-after.png -------------------------------------------------------------------------------- /images/json-reformat-2-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongo/json-reformat/9120ab67c5379c44bc7a7a07ca858670cea4f32f/images/json-reformat-2-before.png -------------------------------------------------------------------------------- /images/json-reformat_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongo/json-reformat/9120ab67c5379c44bc7a7a07ca858670cea4f32f/images/json-reformat_demo.gif -------------------------------------------------------------------------------- /json-reformat.el: -------------------------------------------------------------------------------- 1 | ;;; json-reformat.el --- Reformatting tool for JSON 2 | 3 | ;; Author: Wataru MIYAGUNI4 | ;; URL: https://github.com/gongo/json-reformat 5 | ;; Package-Requires: ((emacs "24.3")) 6 | ;; Version: 0.0.7 7 | ;; Keywords: json 8 | 9 | ;; Copyright (c) 2012 Wataru MIYAGUNI 10 | ;; 11 | ;; MIT License 12 | ;; 13 | ;; Permission is hereby granted, free of charge, to any person obtaining 14 | ;; a copy of this software and associated documentation files (the 15 | ;; "Software"), to deal in the Software without restriction, including 16 | ;; without limitation the rights to use, copy, modify, merge, publish, 17 | ;; distribute, sublicense, and/or sell copies of the Software, and to 18 | ;; permit persons to whom the Software is furnished to do so, subject to 19 | ;; the following conditions: 20 | ;; 21 | ;; The above copyright notice and this permission notice shall be 22 | ;; included in all copies or substantial portions of the Software. 23 | ;; 24 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 28 | ;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 29 | ;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 30 | ;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | 33 | ;;; Commentary: 34 | 35 | ;; json-reformat.el is a reformatting tool for JSON (http://json.org/). 36 | ;; 37 | ;; ## Usage 38 | ;; 39 | ;; 1. Specify region 40 | ;; 2. Call 'M-x json-reformat-region' 41 | ;; 42 | ;; ## Customize 43 | ;; 44 | ;; - `json-reformat:indent-width' 45 | ;; - `json-reformat:pretty-string?' 46 | ;; 47 | 48 | ;;; Code: 49 | 50 | (require 'json) 51 | (eval-when-compile (require 'cl-lib)) 52 | 53 | (unless (require 'subr-x nil t) 54 | ;; built-in subr-x from 24.4 55 | (defsubst hash-table-keys (hash-table) 56 | "Return a list of keys in HASH-TABLE." 57 | (let ((keys '())) 58 | (maphash (lambda (k _v) (push k keys)) hash-table) 59 | keys))) 60 | 61 | (put 'json-reformat-error 'error-message "JSON Reformat error") 62 | (put 'json-reformat-error 'error-conditions '(json-reformat-error error)) 63 | 64 | (defconst json-reformat:special-chars-as-pretty-string 65 | '((?\" . ?\") 66 | (?\\ . ?\\))) 67 | 68 | (defcustom json-reformat:indent-width 4 69 | "How much indentation `json-reformat-region' should do at each level." 70 | :type 'integer 71 | :safe #'integerp 72 | :group 'json-reformat) 73 | 74 | (defcustom json-reformat:pretty-string? nil 75 | "Whether to decode the string. 76 | 77 | Example: 78 | 79 | {\"name\":\"foobar\",\"nick\":\"foo \\u00e4 bar\",\"description\":\" \\nbaz\\n\"} 80 | 81 | If nil: 82 | 83 | { 84 | \"name\": \"foobar\", 85 | \"nick\": \"foo \\u00e4 bar\", 86 | \"description\": \"\\nbaz\\n<\\/pre>\" 87 | } 88 | 89 | Else t: 90 | 91 | { 92 | \"name\": \"foobar\", 93 | \"nick\": \"foo ä bar\", 94 | \"description\": \"95 | baz 96 |\" 97 | }" 98 | :type 'boolean 99 | :safe #'booleanp 100 | :group 'json-reformat) 101 | 102 | (defun json-reformat:indent (level) 103 | (make-string (* level json-reformat:indent-width) ? )) 104 | 105 | (defun json-reformat:number-to-string (val) 106 | (number-to-string val)) 107 | 108 | (defun json-reformat:symbol-to-string (val) 109 | (cond ((equal 't val) "true") 110 | ((equal json-false val) "false") 111 | (t (symbol-name val)))) 112 | 113 | (defun json-reformat:encode-char-as-pretty (char) 114 | (setq char (encode-char char 'ucs)) 115 | (let ((special-char (car (rassoc char json-reformat:special-chars-as-pretty-string)))) 116 | (if special-char 117 | (format "\\%c" special-char) 118 | (format "%c" char)))) 119 | 120 | (defun json-reformat:string-to-string (val) 121 | (if json-reformat:pretty-string? 122 | (format "\"%s\"" (mapconcat 'json-reformat:encode-char-as-pretty val "")) 123 | (json-encode-string val))) 124 | 125 | (defun json-reformat:vector-to-string (val level) 126 | (if (= (length val) 0) "[]" 127 | (concat "[\n" 128 | (mapconcat 129 | 'identity 130 | (cl-loop for v across val 131 | collect (concat 132 | (json-reformat:indent (1+ level)) 133 | (json-reformat:print-node v (1+ level)) 134 | )) 135 | (concat ",\n")) 136 | "\n" (json-reformat:indent level) "]" 137 | ))) 138 | 139 | (defun json-reformat:print-node (val level) 140 | (cond ((hash-table-p val) (json-reformat:tree-to-string (json-reformat:tree-sibling-to-plist val) level)) 141 | ((numberp val) (json-reformat:number-to-string val)) 142 | ((vectorp val) (json-reformat:vector-to-string val level)) 143 | ((null val) "null") 144 | ((symbolp val) (json-reformat:symbol-to-string val)) 145 | (t (json-reformat:string-to-string val)))) 146 | 147 | (defun json-reformat:tree-sibling-to-plist (root) 148 | (let (pl) 149 | (dolist (key (reverse (hash-table-keys root)) pl) 150 | (setq pl (plist-put pl key (gethash key root)))))) 151 | 152 | (defun json-reformat:tree-to-string (root level) 153 | (concat "{\n" 154 | (let (key val str) 155 | (while root 156 | (setq key (car root) 157 | val (cadr root) 158 | root (cddr root)) 159 | (setq str 160 | (concat str (json-reformat:indent (1+ level)) 161 | "\"" key "\"" 162 | ": " 163 | (json-reformat:print-node val (1+ level)) 164 | (when root ",") 165 | "\n" 166 | ))) 167 | str) 168 | (json-reformat:indent level) 169 | "}")) 170 | 171 | (defun json-reformat-from-string (string) 172 | (with-temp-buffer 173 | (insert string) 174 | (goto-char (point-min)) 175 | (condition-case errvar 176 | (let ((json-key-type 'string) 177 | (json-object-type 'hash-table) 178 | json-tree) 179 | (setq json-tree (json-read)) 180 | (json-reformat:print-node json-tree 0)) 181 | (json-error 182 | (signal 'json-reformat-error 183 | (list (error-message-string errvar) 184 | (line-number-at-pos (point)) 185 | (point))))))) 186 | 187 | ;;;###autoload 188 | (defun json-reformat-region (begin end) 189 | "Reformat the JSON in the specified region. 190 | 191 | If you want to customize the reformat style, 192 | please see the documentation of `json-reformat:indent-width' 193 | and `json-reformat:pretty-string?'." 194 | (interactive "*r") 195 | (let ((start-line (line-number-at-pos begin)) 196 | (start-pos begin)) 197 | (save-excursion 198 | (save-restriction 199 | (narrow-to-region begin end) 200 | (goto-char (point-min)) 201 | (let (reformatted) 202 | (condition-case errvar 203 | (progn 204 | (setq reformatted 205 | (json-reformat-from-string 206 | (buffer-substring-no-properties (point-min) (point-max)))) 207 | (delete-region (point-min) (point-max)) 208 | (insert reformatted)) 209 | (json-reformat-error 210 | (let ((reason (nth 1 errvar)) 211 | (line (nth 2 errvar)) 212 | (position (nth 3 errvar))) 213 | (message 214 | "JSON parse error [Reason] %s [Position] In buffer, line %d (char %d)" 215 | reason 216 | (+ start-line line -1) 217 | (+ start-pos position -1)))))))))) 218 | 219 | (provide 'json-reformat) 220 | 221 | ;;; json-reformat.el ends here 222 | -------------------------------------------------------------------------------- /test/json-reformat-test.el: -------------------------------------------------------------------------------- 1 | (ert-deftest json-reformat-test:indent () 2 | ;; default 4 3 | (should (string= "" (json-reformat:indent 0))) 4 | (should (string= " " (json-reformat:indent 1))) 5 | (should (string= " " (json-reformat:indent 2))) 6 | 7 | ;; specify `json-reformat:indent-width' 8 | (let ((json-reformat:indent-width 3)) 9 | (should (string= "" (json-reformat:indent 0))) 10 | (should (string= " " (json-reformat:indent 1))) 11 | (should (string= " " (json-reformat:indent 2)))) 12 | ) 13 | 14 | (ert-deftest json-reformat-test:number-to-string () 15 | (should (string= "1" (json-reformat:number-to-string 1))) 16 | (should (string= "100" (json-reformat:number-to-string 100))) 17 | (should (string= "-1" (json-reformat:number-to-string -1))) 18 | ) 19 | 20 | (ert-deftest json-reformat-test:symbol-to-string () 21 | (should (string= "true" (json-reformat:symbol-to-string t))) 22 | (should (string= "false" (json-reformat:symbol-to-string :json-false))) 23 | (should (string= "foo" (json-reformat:symbol-to-string 'foo))) 24 | (should (string= ":bar" (json-reformat:symbol-to-string :bar))) 25 | ) 26 | 27 | (ert-deftest json-reformat-test:vector-to-string () 28 | (should (string= "\ 29 | \[ 30 | 1, 31 | 2, 32 | 3 33 | \]" (json-reformat:vector-to-string [1 2 3] 0))) 34 | 35 | (should (string= "\ 36 | \[ 37 | \"foo\", 38 | \"bar\", 39 | 3 40 | \]" (json-reformat:vector-to-string ["foo" "bar" 3] 1))) 41 | 42 | (should (string= "\ 43 | \[ 44 | 1, 45 | \[ 46 | 2, 47 | \[ 48 | 3, 49 | 4 50 | \], 51 | 5 52 | \], 53 | 6, 54 | [] 55 | \]" (json-reformat:vector-to-string [1 [2 [3 4] 5] 6 []] 0))) 56 | ) 57 | 58 | (ert-deftest json-reformat-test:string-to-string () 59 | (should (string= "\"foobar\"" (json-reformat:string-to-string "foobar"))) 60 | (should (string= "\"fo\\\"o\\nbar\"" (json-reformat:string-to-string "fo\"o\nbar"))) 61 | (should (string= "\"\\u2661\"" (json-reformat:string-to-string "\u2661"))) 62 | 63 | (should (string= "\"^(amq\\\\.gen.*|amq\\\\.default)$\"" (json-reformat:string-to-string "^(amq\\.gen.*|amq\\.default)$"))) 64 | ) 65 | 66 | (ert-deftest json-reformat-test:string-to-string-when-pretty () 67 | (let ((json-reformat:pretty-string? t)) 68 | (should (string= "\"foobar\"" (json-reformat:string-to-string "foobar"))) 69 | (should (string= "\"fo\\\"o 70 | bar\"" (json-reformat:string-to-string "fo\"o\nbar"))) 71 | (should (string= "\"♡\"" (json-reformat:string-to-string "\u2661"))) 72 | 73 | (should (string= "\"^(amq\\\\.gen.*|amq\\\\.default)$\"" (json-reformat:string-to-string "^(amq\\.gen.*|amq\\.default)$"))) 74 | )) 75 | 76 | (ert-deftest json-reformat-test:print-node () 77 | (should (string= "null" (json-reformat:print-node nil 0))) 78 | ) 79 | 80 | (ert-deftest json-reformat-test:tree-to-string () 81 | (let ((info (make-hash-table :test 'equal))) 82 | (puthash "male" t info) 83 | (should (string= "\ 84 | { 85 | \"info\": { 86 | \"male\": true 87 | }, 88 | \"age\": 33, 89 | \"name\": \"John Smith\" 90 | }" (json-reformat:tree-to-string 91 | `("info" ,info "age" 33 "name" "John Smith") 0))) 92 | )) 93 | 94 | (ert-deftest json-reformat-test:json-reformat-region () 95 | (should (string= "\ 96 | { 97 | \"name\": \"John Smith\", 98 | \"age\": 33, 99 | \"breakfast\": \[ 100 | \"milk\", 101 | \"bread\", 102 | \"egg\" 103 | \] 104 | }" (with-temp-buffer 105 | (insert "{\"name\": \"John Smith\", \"age\": 33, \"breakfast\":\[\"milk\", \"bread\", \"egg\"\]}") 106 | (json-reformat-region (point-min) (point-max)) 107 | (buffer-string)))) 108 | 109 | (should (string= "\ 110 | \[ 111 | { 112 | \"foo\": \"bar\" 113 | }, 114 | { 115 | \"foo\": \"baz\" 116 | } 117 | \]" (with-temp-buffer 118 | (insert "[{ \"foo\" : \"bar\" }, { \"foo\" : \"baz\" }]") 119 | (json-reformat-region (point-min) (point-max)) 120 | (buffer-string)))) 121 | 122 | (should (string= "\ 123 | { 124 | \"foo\": { 125 | }, 126 | \"bar\": null 127 | }" (with-temp-buffer 128 | (insert "{\"foo\" : {}, \"bar\" : null}") 129 | (json-reformat-region (point-min) (point-max)) 130 | (buffer-string))))) 131 | 132 | (ert-deftest json-reformat-test:json-reformat-region-occur-error () 133 | (let (message-string) 134 | (cl-letf (((symbol-function 'message) (lambda (&rest args) (setq message-string (apply 'format args))))) 135 | ;; missing camma after "milk" 136 | (with-temp-buffer 137 | (insert "{\"name\": \"John Smith\", \"age\": 33, \"breakfast\":\[\"milk\" \"bread\", \"egg\"\]}") 138 | (json-reformat-region (point-min) (point-max))) 139 | (should (string= 140 | "JSON parse error [Reason] Unknown JSON error: bleah [Position] In buffer, line 1 (char 55)" 141 | message-string)) 142 | 143 | ;; The `foo' key don't start with '"' 144 | (with-temp-buffer 145 | (insert "\ 146 | 147 | 148 | [{ foo : \"bar\" }, { \"foo\" : \"baz\" }]") ;; At 3 (line) 149 | (json-reformat-region (point-min) (point-max))) 150 | (should (string= 151 | "JSON parse error [Reason] Bad string format: \"doesn't start with '\\\"'!\" [Position] In buffer, line 3 (char 6)" 152 | message-string)) 153 | ))) 154 | -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | (require 'ert) 2 | (require 'undercover) 3 | (undercover "json-reformat.el") 4 | 5 | (require 'json-reformat) 6 | --------------------------------------------------------------------------------