;;; json-reformat.el --- Reformatting tool for JSON ;; Author: Wataru MIYAGUNI ;; URL: https://github.com/gongo/json-reformat ;; Package-Version: 20160212.53 ;; 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\":\"
\\nbaz\\n
\"} If nil: { \"name\": \"foobar\", \"nick\": \"foo \\u00e4 bar\", \"description\": \"
\\nbaz\\n<\\/pre>\"
    }

Else t:

    {
        \"name\": \"foobar\",
        \"nick\": \"foo รค bar\",
        \"description\": \"
    baz
    
\" }" :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