Commit 1e363a8e authored by Simen Heggestøyl's avatar Simen Heggestøyl

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.
parent 9dd7da99
......@@ -332,6 +332,10 @@ unlike `bookmark-set' which silently updates an existing bookmark.
---
*** `json-pretty-print' and `json-pretty-print-buffer' now maintain
the ordering of object keys by default.
---
*** New commands `json-pretty-print-ordered' and
`json-pretty-print-buffer-ordered' pretty prints JSON objects with
object keys sorted alphabetically.
** You can recompute the VC state of a file buffer with `M-x vc-refresh-state'
** Prog mode has some support for multi-mode indentation.
......
......@@ -52,6 +52,8 @@
;;; Code:
(require 'map)
;; Parameters
(defvar json-object-type 'alist
......@@ -111,6 +113,13 @@ Used only when `json-encoding-pretty-print' is non-nil.")
"If non-nil, ] and } closings will be formatted lisp-style,
without indentation.")
(defvar json-encoding-object-sort-predicate nil
"Sorting predicate for JSON object keys during encoding.
If nil, no sorting is performed. Else, JSON object keys are
ordered by the specified sort predicate during encoding. For
instance, setting this to `string<' will have JSON object keys
ordered alphabetically.")
(defvar json-pre-element-read-function nil
"Function called (if non-nil) by `json-read-array' and
`json-read-object' right before reading a JSON array or object,
......@@ -159,6 +168,15 @@ Unlike `reverse', this keeps the property-value pairs intact."
(push prop res)))
res))
(defun json--plist-to-alist (plist)
"Return an alist of the property-value pairs in PLIST."
(let (res)
(while plist
(let ((prop (pop plist))
(val (pop plist)))
(push (cons prop val) res)))
(nreverse res)))
(defmacro json--with-indentation (body)
`(let ((json--encoding-current-indentation
(if json-encoding-pretty-print
......@@ -492,32 +510,39 @@ Please see the documentation of `json-object-type' and `json-key-type'."
(defun json-encode-hash-table (hash-table)
"Return a JSON representation of HASH-TABLE."
(format "{%s%s}"
(json-join
(let (r)
(json--with-indentation
(maphash
(lambda (k v)
(push (format
(if json-encoding-pretty-print
"%s%s: %s"
"%s%s:%s")
json--encoding-current-indentation
(json-encode-key k)
(json-encode v))
r))
hash-table))
r)
json-encoding-separator)
(if (or (not json-encoding-pretty-print)
json-encoding-lisp-style-closings)
""
json--encoding-current-indentation)))
(if json-encoding-object-sort-predicate
(json-encode-alist (map-into hash-table 'list))
(format "{%s%s}"
(json-join
(let (r)
(json--with-indentation
(maphash
(lambda (k v)
(push (format
(if json-encoding-pretty-print
"%s%s: %s"
"%s%s:%s")
json--encoding-current-indentation
(json-encode-key k)
(json-encode v))
r))
hash-table))
r)
json-encoding-separator)
(if (or (not json-encoding-pretty-print)
json-encoding-lisp-style-closings)
""
json--encoding-current-indentation))))
;; List encoding (including alists and plists)
(defun json-encode-alist (alist)
"Return a JSON representation of ALIST."
(when json-encoding-object-sort-predicate
(setq alist
(sort alist (lambda (a b)
(funcall json-encoding-object-sort-predicate
(car a) (car b))))))
(format "{%s%s}"
(json-join
(json--with-indentation
......@@ -537,25 +562,27 @@ Please see the documentation of `json-object-type' and `json-key-type'."
(defun json-encode-plist (plist)
"Return a JSON representation of PLIST."
(let (result)
(json--with-indentation
(while plist
(push (concat
json--encoding-current-indentation
(json-encode-key (car plist))
(if json-encoding-pretty-print
": "
":")
(json-encode (cadr plist)))
result)
(setq plist (cddr plist))))
(concat "{"
(json-join (nreverse result) json-encoding-separator)
(if (and json-encoding-pretty-print
(not json-encoding-lisp-style-closings))
(if json-encoding-object-sort-predicate
(json-encode-alist (json--plist-to-alist plist))
(let (result)
(json--with-indentation
(while plist
(push (concat
json--encoding-current-indentation
"")
"}")))
(json-encode-key (car plist))
(if json-encoding-pretty-print
": "
":")
(json-encode (cadr plist)))
result)
(setq plist (cddr plist))))
(concat "{"
(json-join (nreverse result) json-encoding-separator)
(if (and json-encoding-pretty-print
(not json-encoding-lisp-style-closings))
json--encoding-current-indentation
"")
"}"))))
(defun json-encode-list (list)
"Return a JSON representation of LIST.
......@@ -698,6 +725,18 @@ Advances point just past JSON object."
(txt (delete-and-extract-region begin end)))
(insert (json-encode (json-read-from-string txt))))))
(defun json-pretty-print-buffer-ordered ()
"Pretty-print current buffer with object keys ordered."
(interactive)
(let ((json-encoding-object-sort-predicate 'string<))
(json-pretty-print-buffer)))
(defun json-pretty-print-ordered (begin end)
"Pretty-print the region with object keys ordered."
(interactive "r")
(let ((json-encoding-object-sort-predicate 'string<))
(json-pretty-print begin end)))
(provide 'json)
;;; json.el ends here
......@@ -28,11 +28,40 @@
(should (equal (json--plist-reverse '(:a 1 :b 2 :c 3))
'(:c 3 :b 2 :a 1))))
(ert-deftest test-json-plist-to-alist ()
(should (equal (json--plist-to-alist '()) '()))
(should (equal (json--plist-to-alist '(:a 1)) '((:a . 1))))
(should (equal (json--plist-to-alist '(:a 1 :b 2 :c 3))
'((:a . 1) (:b . 2) (:c . 3)))))
(ert-deftest test-json-encode-plist ()
(let ((plist '(:a 1 :b 2)))
(should (equal (json-encode plist) "{\"a\":1,\"b\":2}"))))
(ert-deftest json-encode-simple-alist ()
(should (equal (json-encode '((a . 1)
(b . 2)))
"{\"a\":1,\"b\":2}")))
(ert-deftest test-json-encode-hash-table ()
(let ((hash-table (make-hash-table))
(json-encoding-object-sort-predicate 'string<))
(puthash :a 1 hash-table)
(puthash :b 2 hash-table)
(puthash :c 3 hash-table)
(should (equal (json-encode hash-table)
"{\"a\":1,\"b\":2,\"c\":3}"))))
(ert-deftest test-json-encode-alist-with-sort-predicate ()
(let ((alist '((:c . 3) (:a . 1) (:b . 2)))
(json-encoding-object-sort-predicate 'string<))
(should (equal (json-encode alist) "{\"a\":1,\"b\":2,\"c\":3}"))))
(ert-deftest test-json-encode-plist-with-sort-predicate ()
(let ((plist '(:c 3 :a 1 :b 2))
(json-encoding-object-sort-predicate 'string<))
(should (equal (json-encode plist) "{\"a\":1,\"b\":2,\"c\":3}"))))
(ert-deftest json-read-simple-alist ()
(let ((json-object-type 'alist))
(should (equal (json-read-from-string "{\"a\": 1, \"b\": 2}")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment