Dawei Ma / May 23 2021 / Published
Remix of Clojure by
Nextjournal
Learn AST - Clojure
Context
Convert json value to key path by changing AST.

{:deps {org.clojure/clojure {:mvn/version "1.10.1"} ;; complient is used for autocompletion ;; add your libs here (and restart the runtime to pick up changes) compliment/compliment {:mvn/version "0.3.9"} cheshire/cheshire {:mvn/version "5.10.0"} instaparse/instaparse {:mvn/version "1.4.10"}}}deps.edn
Extensible Data Notation
{:hello (clojure-version)}0.0s
Clojure
Test Data
(def json-data "[{ \"k1\": \"v1\", \"k2\": { \"k21\": \"v21\", \"k22\": \"v22\" } }, { \"k4\": \"v4\", \"k5\": \"v5\", \"k6\": \"v6\"}]")(println json-data)0.4s
Clojure
AST
(require [instaparse.core :as insta])(def parser (insta/parser "JSON = ARRAY|OBJECT<VALUE> = NULL|NUMBER|STRING|TRUE|FALSE|ARRAY|OBJECTARRAY = BRACKET_OPEN VALUE WHITESPACE* (COMMA WHITESPACE* VALUE)* BRACKET_CLOSE TERMINAL*<BRACKET_OPEN> = <'['><BRACKET_CLOSE> = <']'><WHITESPACE> = <#'\\s+'><COMMA> = <','>KEY_VALUE_PAIR = STRING WHITESPACE* COLON WHITESPACE* VALUEOBJECT = CURLY_OPEN WHITESPACE* KEY_VALUE_PAIR WHITESPACE* (COMMA WHITESPACE* KEY_VALUE_PAIR WHITESPACE*)* CURLY_CLOSE TERMINAL*<CURLY_OPEN> = <'{'><CURLY_CLOSE> = <'}'><COLON> = <':'>STRING = #'\"[^\"]+\"'NULL = <#'null'>NUMBER = #'\\d+'TRUE = <#'true'>FALSE = <#'false'><TERMINAL> = <#'\\n'>"))7.0s
Clojure
; comment for no need;(insta/visualize (parser (slurp "/path/to/output/test.json")) :output-file "/path/to/output/test.png" :options {:dpi 63});(parser (slurp "/path/to/output/test.json"))0.0s
Clojure

(def json-tree (parser json-data))(print json-tree)0.5s
Clojure
Tree Trave 0 (failure version)
(defn transform-key [x] (-> x (clojure.string/replace "\"" "") (clojure.string/upper-case)))(defn bf "return elements in tree, breath-first" [[el left right]] ;; a tree is a seq of one element, ;; followed by left and right child trees (if (nil? el) "" (if (= el :OBJECT) (do (print "{") (bf left) (bf right) (print "}, ")) (if (= el :KEY_VALUE_PAIR) (do (print (str (transform-key (get left 1)) ": ")) (bf right)) (print left))))) (defn transform-value [x] (if (= x "") x (str x "."))) (defn bf2 "return elements in tree, breath-first" [[el left right] path] ;; a tree is a seq of one element, ;; followed by left and right child trees (if (nil? el) "" (if (= el :OBJECT) (do (print "{") (bf2 left path) (bf2 right path) (print "}, ")) (if (= el :KEY_VALUE_PAIR) (do (print (str (transform-key (get left 1)) ": ")) (bf2 right (str (transform-value path) (transform-key (get left 1))))) (print (str "\"" path "\", ")))))) (defn bf3 "return elements in tree, breath-first" [[el & children :as tree] path] ;; a tree is a seq of one element, ;; followed by left and right child trees (if (nil? el) (do) (if (= el :JSON) (do (print (get (vec children) 0)) (bf3 (get (vec children) 0) path)) (if (= el :ARRAY) (let [[first & rest-list] (get (vec children) 0) rest (vec rest-list)] (do (print "[") (bf3 first path) (bf3 rest path) (print "], "))) (if (= el :OBJECT) (let [[_ [_ key] & rest-list] (get (vec children) 0) [rest] (vec rest-list)] (do (print "{") (print (str (transform-key key) ": ")) (bf3 rest (transform-key key)) (print "}, "))) (if (= el :KEY_VALUE_PAIR) (do (print (str (transform-key (get first 1)) ": ")) (bf3 rest (str (transform-value path) (transform-key (get first 1))))) (print (str "\"" path "\", ")))))))) (bf3 json-tree "")1.0s
Clojure
Related SO Question:
Tree Travel 1 (@Chris)
(declare g h)(defn f [[_ body]] (g body)) (defn g [[type & value]] (condp = type :ARRAY (map g value) :OBJECT (reduce h {} value) :STRING (first value)))(defn h [m [_ [_ k] v]] (assoc m k (g v))) (print (f json-tree))0.4s
(defn kt [k] (clojure.string/replace k "\"" ""))(defn g [[type & value] & [prev-key]] (condp = type :ARRAY (map g value) :OBJECT (reduce (fn [m [_ [_ k] v]] (let [[sub-type & sub-value] v curr-key (if prev-key (str (kt prev-key) "." (kt k)) (kt k))] (assoc m (kt k) (condp = sub-type :STRING curr-key (g v curr-key))))) {} value)))(defn f [[_ body]] (g body))(def json-map (f json-tree))0.1s
(require [cheshire.core :as json])(json/generate-string json-map {:pretty true})2.1s
Clojure
Tree Travel 2 (@nt)
(require [cheshire.core :as json])(def r (json/parse-string json-data keyword))(clojure.pprint/pprint r)0.8s
Clojure
(defn key-paths "映射key路径" [pre-key x] (cond (map? x) (->> x (map (fn [[k v]] [k (key-paths (if pre-key (str pre-key "." (name k)) (name k)) v)])) (into {})) (sequential? x) (mapv (key-paths pre-key %) x) :else pre-key))(def map-key-paths (key-paths nil %))0.1s
Clojure
(clojure.pprint/pprint (map-key-paths r))0.4s
Clojure
Final Version
(require [instaparse.core :as insta])(require [cheshire.core :as json])(def parser (insta/parser "JSON = ARRAY|OBJECT<VALUE> = NULL|NUMBER|STRING|TRUE|FALSE|ARRAY|OBJECTARRAY = BRACKET_OPEN VALUE WHITESPACE* (COMMA WHITESPACE* VALUE)* BRACKET_CLOSE TERMINAL*<BRACKET_OPEN> = <'['><BRACKET_CLOSE> = <']'><WHITESPACE> = <#'\\s+'><COMMA> = <','>KEY_VALUE_PAIR = STRING WHITESPACE* COLON WHITESPACE* VALUEOBJECT = CURLY_OPEN WHITESPACE* KEY_VALUE_PAIR WHITESPACE* (COMMA WHITESPACE* KEY_VALUE_PAIR WHITESPACE*)* CURLY_CLOSE TERMINAL*<CURLY_OPEN> = <'{'><CURLY_CLOSE> = <'}'><COLON> = <':'>STRING = #'\"[^\"]+\"'NULL = <#'null'>NUMBER = #'\\d+'TRUE = <#'true'>FALSE = <#'false'><TERMINAL> = <#'\\n'>"))(def json-tree (parser (slurp "./input.json")))(defn kt [k] (clojure.string/replace k "\"" ""))(defn g [[type & value] & [prev-key]] (condp = type :ARRAY (map g value) :OBJECT (reduce (fn [m [_ [_ k] v]] (let [[sub-type & sub-value] v curr-key (if prev-key (str (kt prev-key) "." (kt k)) (kt k))] (assoc m (kt k) (condp = sub-type :STRING curr-key (g v curr-key))))) {} value)))(defn f [[_ body]] (g body))(def json-map (f json-tree))(json/generate-string json-map {:pretty true})Clojure
After run and get the output, just go https://www.freeformatter.com/json-escape.html to UNESCAPE the content and get final json.