aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimen Heggestøyl2015-11-12 18:30:37 +0100
committerSimen Heggestøyl2015-11-12 18:30:37 +0100
commit1e363a8ea5ac09455f3a44fbb646b5af32bca51c (patch)
tree7b9ab75057951d32892cb50a31c5880c941caf23
parent9dd7da9945c16aa343080a535ed74eeecf769fd1 (diff)
downloademacs-1e363a8ea5ac09455f3a44fbb646b5af32bca51c.tar.gz
emacs-1e363a8ea5ac09455f3a44fbb646b5af32bca51c.zip
Enable sorting of JSON object keys when encoding
* lisp/json.el (json-encoding-object-sort-predicate): New variable for specifying a sorting predicate for JSON objects during encoding. (json--plist-to-alist): New utility function. (json-encode-hash-table): Re-use `json-encode-alist' when object keys are to be sorted. (json-encode-alist): Sort output by `json-encoding-object-sort-predicate, when set. (json-encode-plist): Re-use `json-encode-alist' when object keys are to be sorted. (json-pretty-print-buffer-ordered): New command to pretty print the buffer with object keys sorted alphabetically. (json-pretty-print-ordered): New command to pretty print the region with object keys sorted alphabetically. * test/automated/json-tests.el (test-json-plist-to-alist) (test-json-encode-plist, test-json-encode-hash-table) (test-json-encode-alist-with-sort-predicate) (test-json-encode-plist-with-sort-predicate): New tests. * etc/NEWS: Add an entry for the new commands.
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/json.el117
-rw-r--r--test/automated/json-tests.el29
3 files changed, 111 insertions, 39 deletions
diff --git a/etc/NEWS b/etc/NEWS
index f3df92e51e5..46910b021c7 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -332,6 +332,10 @@ unlike `bookmark-set' which silently updates an existing bookmark.
332--- 332---
333*** `json-pretty-print' and `json-pretty-print-buffer' now maintain 333*** `json-pretty-print' and `json-pretty-print-buffer' now maintain
334the ordering of object keys by default. 334the ordering of object keys by default.
335---
336*** New commands `json-pretty-print-ordered' and
337`json-pretty-print-buffer-ordered' pretty prints JSON objects with
338object keys sorted alphabetically.
335 339
336** You can recompute the VC state of a file buffer with `M-x vc-refresh-state' 340** You can recompute the VC state of a file buffer with `M-x vc-refresh-state'
337** Prog mode has some support for multi-mode indentation. 341** Prog mode has some support for multi-mode indentation.
diff --git a/lisp/json.el b/lisp/json.el
index 97cf9934c34..0214a3e3a4d 100644
--- a/lisp/json.el
+++ b/lisp/json.el
@@ -52,6 +52,8 @@
52 52
53;;; Code: 53;;; Code:
54 54
55(require 'map)
56
55;; Parameters 57;; Parameters
56 58
57(defvar json-object-type 'alist 59(defvar json-object-type 'alist
@@ -111,6 +113,13 @@ Used only when `json-encoding-pretty-print' is non-nil.")
111 "If non-nil, ] and } closings will be formatted lisp-style, 113 "If non-nil, ] and } closings will be formatted lisp-style,
112without indentation.") 114without indentation.")
113 115
116(defvar json-encoding-object-sort-predicate nil
117 "Sorting predicate for JSON object keys during encoding.
118If nil, no sorting is performed. Else, JSON object keys are
119ordered by the specified sort predicate during encoding. For
120instance, setting this to `string<' will have JSON object keys
121ordered alphabetically.")
122
114(defvar json-pre-element-read-function nil 123(defvar json-pre-element-read-function nil
115 "Function called (if non-nil) by `json-read-array' and 124 "Function called (if non-nil) by `json-read-array' and
116`json-read-object' right before reading a JSON array or object, 125`json-read-object' right before reading a JSON array or object,
@@ -159,6 +168,15 @@ Unlike `reverse', this keeps the property-value pairs intact."
159 (push prop res))) 168 (push prop res)))
160 res)) 169 res))
161 170
171(defun json--plist-to-alist (plist)
172 "Return an alist of the property-value pairs in PLIST."
173 (let (res)
174 (while plist
175 (let ((prop (pop plist))
176 (val (pop plist)))
177 (push (cons prop val) res)))
178 (nreverse res)))
179
162(defmacro json--with-indentation (body) 180(defmacro json--with-indentation (body)
163 `(let ((json--encoding-current-indentation 181 `(let ((json--encoding-current-indentation
164 (if json-encoding-pretty-print 182 (if json-encoding-pretty-print
@@ -492,32 +510,39 @@ Please see the documentation of `json-object-type' and `json-key-type'."
492 510
493(defun json-encode-hash-table (hash-table) 511(defun json-encode-hash-table (hash-table)
494 "Return a JSON representation of HASH-TABLE." 512 "Return a JSON representation of HASH-TABLE."
495 (format "{%s%s}" 513 (if json-encoding-object-sort-predicate
496 (json-join 514 (json-encode-alist (map-into hash-table 'list))
497 (let (r) 515 (format "{%s%s}"
498 (json--with-indentation 516 (json-join
499 (maphash 517 (let (r)
500 (lambda (k v) 518 (json--with-indentation
501 (push (format 519 (maphash
502 (if json-encoding-pretty-print 520 (lambda (k v)
503 "%s%s: %s" 521 (push (format
504 "%s%s:%s") 522 (if json-encoding-pretty-print
505 json--encoding-current-indentation 523 "%s%s: %s"
506 (json-encode-key k) 524 "%s%s:%s")
507 (json-encode v)) 525 json--encoding-current-indentation
508 r)) 526 (json-encode-key k)
509 hash-table)) 527 (json-encode v))
510 r) 528 r))
511 json-encoding-separator) 529 hash-table))
512 (if (or (not json-encoding-pretty-print) 530 r)
513 json-encoding-lisp-style-closings) 531 json-encoding-separator)
514 "" 532 (if (or (not json-encoding-pretty-print)
515 json--encoding-current-indentation))) 533 json-encoding-lisp-style-closings)
534 ""
535 json--encoding-current-indentation))))
516 536
517;; List encoding (including alists and plists) 537;; List encoding (including alists and plists)
518 538
519(defun json-encode-alist (alist) 539(defun json-encode-alist (alist)
520 "Return a JSON representation of ALIST." 540 "Return a JSON representation of ALIST."
541 (when json-encoding-object-sort-predicate
542 (setq alist
543 (sort alist (lambda (a b)
544 (funcall json-encoding-object-sort-predicate
545 (car a) (car b))))))
521 (format "{%s%s}" 546 (format "{%s%s}"
522 (json-join 547 (json-join
523 (json--with-indentation 548 (json--with-indentation
@@ -537,25 +562,27 @@ Please see the documentation of `json-object-type' and `json-key-type'."
537 562
538(defun json-encode-plist (plist) 563(defun json-encode-plist (plist)
539 "Return a JSON representation of PLIST." 564 "Return a JSON representation of PLIST."
540 (let (result) 565 (if json-encoding-object-sort-predicate
541 (json--with-indentation 566 (json-encode-alist (json--plist-to-alist plist))
542 (while plist 567 (let (result)
543 (push (concat 568 (json--with-indentation
544 json--encoding-current-indentation 569 (while plist
545 (json-encode-key (car plist)) 570 (push (concat
546 (if json-encoding-pretty-print
547 ": "
548 ":")
549 (json-encode (cadr plist)))
550 result)
551 (setq plist (cddr plist))))
552 (concat "{"
553 (json-join (nreverse result) json-encoding-separator)
554 (if (and json-encoding-pretty-print
555 (not json-encoding-lisp-style-closings))
556 json--encoding-current-indentation 571 json--encoding-current-indentation
557 "") 572 (json-encode-key (car plist))
558 "}"))) 573 (if json-encoding-pretty-print
574 ": "
575 ":")
576 (json-encode (cadr plist)))
577 result)
578 (setq plist (cddr plist))))
579 (concat "{"
580 (json-join (nreverse result) json-encoding-separator)
581 (if (and json-encoding-pretty-print
582 (not json-encoding-lisp-style-closings))
583 json--encoding-current-indentation
584 "")
585 "}"))))
559 586
560(defun json-encode-list (list) 587(defun json-encode-list (list)
561 "Return a JSON representation of LIST. 588 "Return a JSON representation of LIST.
@@ -698,6 +725,18 @@ Advances point just past JSON object."
698 (txt (delete-and-extract-region begin end))) 725 (txt (delete-and-extract-region begin end)))
699 (insert (json-encode (json-read-from-string txt)))))) 726 (insert (json-encode (json-read-from-string txt))))))
700 727
728(defun json-pretty-print-buffer-ordered ()
729 "Pretty-print current buffer with object keys ordered."
730 (interactive)
731 (let ((json-encoding-object-sort-predicate 'string<))
732 (json-pretty-print-buffer)))
733
734(defun json-pretty-print-ordered (begin end)
735 "Pretty-print the region with object keys ordered."
736 (interactive "r")
737 (let ((json-encoding-object-sort-predicate 'string<))
738 (json-pretty-print begin end)))
739
701(provide 'json) 740(provide 'json)
702 741
703;;; json.el ends here 742;;; json.el ends here
diff --git a/test/automated/json-tests.el b/test/automated/json-tests.el
index fa1f5484eec..8f0cd6f0857 100644
--- a/test/automated/json-tests.el
+++ b/test/automated/json-tests.el
@@ -28,11 +28,40 @@
28 (should (equal (json--plist-reverse '(:a 1 :b 2 :c 3)) 28 (should (equal (json--plist-reverse '(:a 1 :b 2 :c 3))
29 '(:c 3 :b 2 :a 1)))) 29 '(:c 3 :b 2 :a 1))))
30 30
31(ert-deftest test-json-plist-to-alist ()
32 (should (equal (json--plist-to-alist '()) '()))
33 (should (equal (json--plist-to-alist '(:a 1)) '((:a . 1))))
34 (should (equal (json--plist-to-alist '(:a 1 :b 2 :c 3))
35 '((:a . 1) (:b . 2) (:c . 3)))))
36
37(ert-deftest test-json-encode-plist ()
38 (let ((plist '(:a 1 :b 2)))
39 (should (equal (json-encode plist) "{\"a\":1,\"b\":2}"))))
40
31(ert-deftest json-encode-simple-alist () 41(ert-deftest json-encode-simple-alist ()
32 (should (equal (json-encode '((a . 1) 42 (should (equal (json-encode '((a . 1)
33 (b . 2))) 43 (b . 2)))
34 "{\"a\":1,\"b\":2}"))) 44 "{\"a\":1,\"b\":2}")))
35 45
46(ert-deftest test-json-encode-hash-table ()
47 (let ((hash-table (make-hash-table))
48 (json-encoding-object-sort-predicate 'string<))
49 (puthash :a 1 hash-table)
50 (puthash :b 2 hash-table)
51 (puthash :c 3 hash-table)
52 (should (equal (json-encode hash-table)
53 "{\"a\":1,\"b\":2,\"c\":3}"))))
54
55(ert-deftest test-json-encode-alist-with-sort-predicate ()
56 (let ((alist '((:c . 3) (:a . 1) (:b . 2)))
57 (json-encoding-object-sort-predicate 'string<))
58 (should (equal (json-encode alist) "{\"a\":1,\"b\":2,\"c\":3}"))))
59
60(ert-deftest test-json-encode-plist-with-sort-predicate ()
61 (let ((plist '(:c 3 :a 1 :b 2))
62 (json-encoding-object-sort-predicate 'string<))
63 (should (equal (json-encode plist) "{\"a\":1,\"b\":2,\"c\":3}"))))
64
36(ert-deftest json-read-simple-alist () 65(ert-deftest json-read-simple-alist ()
37 (let ((json-object-type 'alist)) 66 (let ((json-object-type 'alist))
38 (should (equal (json-read-from-string "{\"a\": 1, \"b\": 2}") 67 (should (equal (json-read-from-string "{\"a\": 1, \"b\": 2}")