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|OBJECT
ARRAY = BRACKET_OPEN VALUE WHITESPACE* (COMMA WHITESPACE* VALUE)* BRACKET_CLOSE TERMINAL*
<BRACKET_OPEN> = <'['>
<BRACKET_CLOSE> = <']'>
<WHITESPACE> = <#'\\s+'>
<COMMA> = <','>
KEY_VALUE_PAIR = STRING WHITESPACE* COLON WHITESPACE* VALUE
OBJECT = 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|OBJECT
ARRAY = BRACKET_OPEN VALUE WHITESPACE* (COMMA WHITESPACE* VALUE)* BRACKET_CLOSE TERMINAL*
<BRACKET_OPEN> = <'['>
<BRACKET_CLOSE> = <']'>
<WHITESPACE> = <#'\\s+'>
<COMMA> = <','>
KEY_VALUE_PAIR = STRING WHITESPACE* COLON WHITESPACE* VALUE
OBJECT = 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.