Last active
December 22, 2023 05:42
-
-
Save thickey/79dc1344f76fcba1460878f8e56d055e to your computer and use it in GitHub Desktop.
Example in Clojure of round-tripping a hierarchical map to/from a flattened version
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns flatmap | |
(:require [clojure.string :as str])) | |
(def default-delimiter \$) | |
;; via https://stackoverflow.com/questions/31704704/depth-first-tree-traversal-accumulation-in-clojure/31709202#31709202 | |
(defn traverse [t] | |
(letfn [(traverse- [path t] | |
(when (seq t) | |
(let [[x & xs] (seq t) | |
[k v] x] | |
(lazy-cat | |
(if (map? v) | |
(traverse- (conj path k) v) | |
[[(conj path k) v]]) | |
(traverse- path xs)))))] | |
(traverse- [] t))) | |
(defn as-str | |
"Turn given argument into string." | |
(^String [^Object x] | |
(as-str \/ x)) | |
(^String [^Character delim ^Object x] | |
(cond | |
(instance? | |
clojure.lang.Named x) (if-let [^String the-ns (namespace x)] | |
(let [^StringBuilder sb (StringBuilder. the-ns)] | |
(.append sb delim) | |
(.append sb (name x)) | |
(.toString sb)) | |
(name x)) | |
(instance? String x) x | |
(nil? x) "" | |
:else (.toString x)))) | |
(defn path->str | |
([path] | |
(path->str default-delimiter path)) | |
([separator path] | |
(->> path | |
(map as-str) | |
(str/join separator)))) | |
(defn str->path | |
([^String s] (str->path (re-pattern (java.util.regex.Pattern/quote (str default-delimiter))) s)) | |
([pattern ^String s] | |
(->> (str/split s pattern) | |
(map keyword)))) | |
(defn flatten-map | |
([m] (flatten-map m path->str)) | |
([m path-str-fn] | |
(let [tvs (traverse m)] | |
(zipmap (map path-str-fn (map first tvs)) | |
(map second tvs))))) | |
(defn inflate-map | |
([m] (inflate-map m str->path)) | |
([m str-path-fn] | |
(reduce-kv (fn [m k v] | |
(assoc-in m (str-path-fn k) v)) {} m))) | |
(comment | |
(def config {:my.app.config | |
#:app{:name "MyApp" | |
:version "1.0" | |
:database | |
#:db{:type "MongoDB" | |
:host "localhost" | |
:port 27017} | |
:logging | |
#:log{:level :info | |
:file "app.log"}}}) | |
(flatten-map config) | |
;; {"my.app.config$app/name" "MyApp", | |
;; "my.app.config$app/version" "1.0", | |
;; "my.app.config$app/database$db/type" "MongoDB", | |
;; "my.app.config$app/database$db/host" "localhost", | |
;; "my.app.config$app/database$db/port" 27017, | |
;; "my.app.config$app/logging$log/level" :info, | |
;; "my.app.config$app/logging$log/file" "app.log"} | |
(inflate-map (flatten-map config)) | |
;; #:app{:name "MyApp", | |
;; :version "1.0", | |
;; :database #:db{:type "MongoDB", :host "localhost", :port 27017}, | |
;; :logging #:log{:level :info, :file "app.log"}}} | |
(= config (-> config flatten-map inflate-map)) | |
(-> config | |
(flatten-map (partial path->str "__HI_MOM__"))) | |
(-> config | |
(flatten-map (partial path->str "__HI_MOM__")) | |
(inflate-map (partial str->path #"__HI_MOM__"))) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment