>From f6ddd3b797d6b0d92a1ffa0f5db59543ac7cdc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simen=20Heggest=C3=B8yl?= Date: Sun, 25 Oct 2015 14:44:59 +0100 Subject: [PATCH] Add support for retrieving paths to JSON elements Add support for retrieving the path to a JSON element. This can for instance be useful to retrieve paths in deeply nested JSON structures. * lisp/json.el (json-path-to-position): New function for retrieving the path to a JSON object at a given position. (json--path-to-position, json--path): New variables, used internally by `json-path-to-position'. (json-read-object, json-read-array): Respect `json--path-to-position'. * test/automated/json-tests.el (test-json-path-to-position-with-objects) (test-json-path-to-position-with-arrays): New tests for `json-path-to-position'. --- lisp/json.el | 67 ++++++++++++++++++++++++++++++++++++++++++-- test/automated/json-tests.el | 14 +++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/lisp/json.el b/lisp/json.el index b23d12a..2c82eee 100644 --- a/lisp/json.el +++ b/lisp/json.el @@ -196,6 +196,49 @@ 'json-end-of-file +;;; Paths + +(defvar json--path-to-position nil + "When set to a position, `json-read' will return the path to +the JSON element at the specified position, instead of returning +the parsed JSON object.") + +(defvar json--path '() + "Used internally by `json-path-to-position' to keep track of +the path during recursive calls to `json-read'.") + +(defun json-path-to-position (position &optional string) + "Return the path to the JSON element at POSITION. + +When STRING is provided, return the path to the position in the +string, else to the position in the current buffer. + +The return value is a property list with the following +properties: + +:path -- A list of strings and numbers forming the path to + the JSON element at the given position. Strings + denote object names, while numbers denote array + indexes. + +:match-start -- Position where the matched JSON element begins. + +:match-end -- Position where the matched JSON element ends. + +This can for instance be useful to determine the path to a JSON +element in a deeply nested structure." + (save-excursion + (unless string + (goto-char (point-min))) + (let* ((json--path '()) + (json--path-to-position position) + (path (catch :json-path + (if string + (json-read-from-string string) + (json-read))))) + (when (plist-get path :path) + path)))) + ;;; Keywords (defvar json-keywords '("true" "false" "null") @@ -399,11 +442,21 @@ json-read-object (while (not (char-equal (json-peek) ?})) (json-skip-whitespace) (setq key (json-read-string)) + (when json--path-to-position + (push key json--path)) (json-skip-whitespace) (if (char-equal (json-peek) ?:) (json-advance) (signal 'json-object-format (list ":" (json-peek)))) - (setq value (json-read)) + (json-skip-whitespace) + (let ((start (point))) + (setq value (json-read)) + (when json--path-to-position + (when (< start json--path-to-position (+ (point) 1)) + (throw :json-path (list :path (nreverse json--path) + :match-start start + :match-end (point)))) + (pop json--path))) (setq elements (json-add-to-object elements key value)) (json-skip-whitespace) (unless (char-equal (json-peek) ?}) @@ -509,7 +562,17 @@ json-read-array ;; read values until "]" (let (elements) (while (not (char-equal (json-peek) ?\])) - (push (json-read) elements) + (json-skip-whitespace) + (when json--path-to-position + (push (length elements) json--path)) + (let ((start (point))) + (push (json-read) elements) + (when json--path-to-position + (when (< start json--path-to-position (+ (point) 1)) + (throw :json-path (list :path (nreverse json--path) + :match-start start + :match-end (point)))) + (pop json--path))) (json-skip-whitespace) (unless (char-equal (json-peek) ?\]) (if (char-equal (json-peek) ?,) diff --git a/test/automated/json-tests.el b/test/automated/json-tests.el index d1b7a2f..e0672dd 100644 --- a/test/automated/json-tests.el +++ b/test/automated/json-tests.el @@ -49,5 +49,19 @@ (should (equal (json-read-from-string "\"\\nasd\\u0444\\u044b\\u0432fgh\\t\"") "\nasdфывfgh\t"))) +(ert-deftest test-json-path-to-position-with-objects () + (let* ((json-string "{\"foo\": {\"bar\": {\"baz\": \"value\"}}}") + (matched-path (json-path-to-position 32 json-string))) + (should (equal (plist-get matched-path :path) '("foo" "bar" "baz"))) + (should (equal (plist-get matched-path :match-start) 25)) + (should (equal (plist-get matched-path :match-end) 32)))) + +(ert-deftest test-json-path-to-position-with-arrays () + (let* ((json-string "{\"foo\": [\"bar\", [\"baz\"]]}") + (matched-path (json-path-to-position 20 json-string))) + (should (equal (plist-get matched-path :path) '("foo" 1 0))) + (should (equal (plist-get matched-path :match-start) 18)) + (should (equal (plist-get matched-path :match-end) 23)))) + (provide 'json-tests) ;;; json-tests.el ends here -- 2.6.1