diff options
| author | Simen Heggestøyl | 2015-11-12 18:30:37 +0100 |
|---|---|---|
| committer | Simen Heggestøyl | 2015-11-12 18:30:37 +0100 |
| commit | 1e363a8ea5ac09455f3a44fbb646b5af32bca51c (patch) | |
| tree | 7b9ab75057951d32892cb50a31c5880c941caf23 | |
| parent | 9dd7da9945c16aa343080a535ed74eeecf769fd1 (diff) | |
| download | emacs-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/NEWS | 4 | ||||
| -rw-r--r-- | lisp/json.el | 117 | ||||
| -rw-r--r-- | test/automated/json-tests.el | 29 |
3 files changed, 111 insertions, 39 deletions
| @@ -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 |
| 334 | the ordering of object keys by default. | 334 | the 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 | ||
| 338 | object 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, |
| 112 | without indentation.") | 114 | without indentation.") |
| 113 | 115 | ||
| 116 | (defvar json-encoding-object-sort-predicate nil | ||
| 117 | "Sorting predicate for JSON object keys during encoding. | ||
| 118 | If nil, no sorting is performed. Else, JSON object keys are | ||
| 119 | ordered by the specified sort predicate during encoding. For | ||
| 120 | instance, setting this to `string<' will have JSON object keys | ||
| 121 | ordered 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}") |