;;; json-reformat.el --- Reformatting tool for JSON
;; Author: Wataru MIYAGUNI <gonngo@gmail.com>
;; URL: https://github.com/gongo/json-reformat
;; Package-Version: 20160212.853
;; Version: 0.0.6
;; Keywords: json
;; Copyright (c) 2012 Wataru MIYAGUNI
;;
;; MIT License
;;
;; Permission is hereby granted, free of charge, to any person obtaining
;; a copy of this software and associated documentation files (the
;; "Software"), to deal in the Software without restriction, including
;; without limitation the rights to use, copy, modify, merge, publish,
;; distribute, sublicense, and/or sell copies of the Software, and to
;; permit persons to whom the Software is furnished to do so, subject to
;; the following conditions:
;;
;; The above copyright notice and this permission notice shall be
;; included in all copies or substantial portions of the Software.
;;
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
;;; Commentary:
;; json-reformat.el is a reformatting tool for JSON (http://json.org/).
;;
;; ## Usage
;;
;; 1. Specify region
;; 2. Call 'M-x json-reformat-region'
;;
;; ## Customize
;;
;; - `json-reformat:indent-width'
;; - `json-reformat:pretty-string?'
;;
;;; Code:
(require 'json)
(eval-when-compile (require 'cl))
(unless (require 'subr-x nil t)
;; built-in subr-x from 24.4
(defsubst hash-table-keys (hash-table)
"Return a list of keys in HASH-TABLE."
(let ((keys '()))
(maphash (lambda (k _v) (push k keys)) hash-table)
keys)))
(put 'json-reformat-error 'error-message "JSON Reformat error")
(put 'json-reformat-error 'error-conditions '(json-reformat-error error))
(defconst json-reformat:special-chars-as-pretty-string
'((?\" . ?\")
(?\\ . ?\\)))
(defcustom json-reformat:indent-width 4
"How much indentation `json-reformat-region' should do at each level."
:type 'integer
:safe #'integerp
:group 'json-reformat)
(defcustom json-reformat:pretty-string? nil
"Whether to decode the string.
Example:
{\"name\":\"foobar\",\"nick\":\"foo \\u00e4 bar\",\"description\":\"<pre>\\nbaz\\n</pre>\"}
If nil:
{
\"name\": \"foobar\",
\"nick\": \"foo \\u00e4 bar\",
\"description\": \"<pre>\\nbaz\\n<\\/pre>\"
}
Else t:
{
\"name\": \"foobar\",
\"nick\": \"foo ä bar\",
\"description\": \"<pre>
baz
</pre>\"
}"
:type 'boolean
:safe #'booleanp
:group 'json-reformat)
(defun json-reformat:indent (level)
(make-string (* level json-reformat:indent-width) ? ))
(defun json-reformat:number-to-string (val)
(number-to-string val))
(defun json-reformat:symbol-to-string (val)
(cond ((equal 't val) "true")
((equal json-false val) "false")
(t (symbol-name val))))
(defun json-reformat:encode-char-as-pretty (char)
(setq char (encode-char char 'ucs))
(let ((special-char (car (rassoc char json-reformat:special-chars-as-pretty-string))))
(if special-char
(format "\\%c" special-char)
(format "%c" char))))
(defun json-reformat:string-to-string (val)
(if json-reformat:pretty-string?
(format "\"%s\"" (mapconcat 'json-reformat:encode-char-as-pretty val ""))
(json-encode-string val)))
(defun json-reformat:vector-to-string (val level)
(if (= (length val) 0) "[]"
(concat "[\n"
(mapconcat
'identity
(loop for v across val
collect (concat
(json-reformat:indent (1+ level))
(json-reformat:print-node v (1+ level))
))
(concat ",\n"))
"\n" (json-reformat:indent level) "]"
)))
(defun json-reformat:print-node (val level)
(cond ((hash-table-p val) (json-reformat:tree-to-string (json-reformat:tree-sibling-to-plist val) level))
((numberp val) (json-reformat:number-to-string val))
((vectorp val) (json-reformat:vector-to-string val level))
((null val) "null")
((symbolp val) (json-reformat:symbol-to-string val))
(t (json-reformat:string-to-string val))))
(defun json-reformat:tree-sibling-to-plist (root)
(let (pl)
(dolist (key (reverse (hash-table-keys root)) pl)
(setq pl (plist-put pl key (gethash key root))))))
(defun json-reformat:tree-to-string (root level)
(concat "{\n"
(let (key val str)
(while root
(setq key (car root)
val (cadr root)
root (cddr root))
(setq str
(concat str (json-reformat:indent (1+ level))
"\"" key "\""
": "
(json-reformat:print-node val (1+ level))
(when root ",")
"\n"
)))
str)
(json-reformat:indent level)
"}"))
(defun json-reformat-from-string (string)
(with-temp-buffer
(insert string)
(goto-char (point-min))
(condition-case errvar
(let ((json-key-type 'string)
(json-object-type 'hash-table)
json-tree)
(setq json-tree (json-read))
(json-reformat:print-node json-tree 0))
(json-error
(signal 'json-reformat-error
(list (error-message-string errvar)
(line-number-at-pos (point))
(point)))))))
;;;###autoload
(defun json-reformat-region (begin end)
"Reformat the JSON in the specified region.
If you want to customize the reformat style,
please see the documentation of `json-reformat:indent-width'
and `json-reformat:pretty-string?'."
(interactive "*r")
(let ((start-line (line-number-at-pos begin))
(start-pos begin))
(save-excursion
(save-restriction
(narrow-to-region begin end)
(goto-char (point-min))
(let (reformatted)
(condition-case errvar
(progn
(setq reformatted
(json-reformat-from-string
(buffer-substring-no-properties (point-min) (point-max))))
(delete-region (point-min) (point-max))
(insert reformatted))
(json-reformat-error
(let ((reason (nth 1 errvar))
(line (nth 2 errvar))
(position (nth 3 errvar)))
(message
"JSON parse error [Reason] %s [Position] In buffer, line %d (char %d)"
reason
(+ start-line line -1)
(+ start-pos position -1))))))))))
(provide 'json-reformat)
;;; json-reformat.el ends here