Skip to content

Instantly share code, notes, and snippets.

@thickey
Last active December 22, 2023 05:42
Show Gist options
  • Save thickey/79dc1344f76fcba1460878f8e56d055e to your computer and use it in GitHub Desktop.
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
(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