I am pretty new to Clojure and still trying to grasp the fundamentals. While experimenting, I had the following problem. I tried to make a nested map and convert this into a JSON Object without the use of an external library, but keep running into a wall. I was wondering if someone could guide / help me to get on the right track.
I want to thank you in advance for taking the time to read this and help me out.
I have the following hash-map in Clojure:
; Creating Hashmap
(def hashmap
[
{ :Mineral-1 {
:Mineral-name {:value "Gold"}
:Color {:value "Golden"}
:Weight {:value 2324.23}
:Count {:value 203}
:Price {:value 20320.49}
:DeliveryContainers {:value-1 {:value 1}
:value-2 {:value 2}
:value-3 {:value 3}}
:Sold {:value true}
}
}
{ :Mineral-2 {
:Mineral-name {:value "Silver"}
:Color {:value "Silvered"}
:Weight {:value 2313.23}
:Count {:value 425}
:Price {:value 12345.12}
:DeliveryContainers {:value-1 {:value 4}
:value-2 {:value 5}
:value-3 {:value 6}}
:Sold {:value false}
}
}
]
)
And I am trying to convert it to the following string:
"
[{
"Minerals": [{
"Mineral-1": {
"Mineral-name": "Gold",
"Color": "Golden",
"Weight": 2324.23,
"Count": 203,
"Price": 20320.49,
"DeliveryContainers": [1, 2, 3]
},
"Mineral-2": {
"Mineral-name": "Silver",
"Color": "Silver",
"Weight": 2342.56,
"Count": 234,
"Price": 23123.23,
"DeliveryContainers": [4, 5, 6]
}
}]
}]"
Assuming you restrict yourself to a limited subset of Clojure structures, here is an example that you can use (if you excuse the poor formatting).
user> (def hmap
[ { :Mineral-1 {
:Mineral-name {:value "Gold"}
:Color {:value "Golden"}
:Weight {:value 2324.23}
:Count {:value 203}
:Price {:value 20320.49}
:DeliveryContainers {:value-1 {:value 1}
:value-2 {:value 2}
:value-3 {:value 3}}
:Sold {:value true} } }
{ :Mineral-2 {
:Mineral-name {:value "Silver"}
:Color {:value "Silvered"}
:Weight {:value 2313.23}
:Count {:value 425}
:Price {:value 12345.12}
:DeliveryContainers {:value-1 {:value 4}
:value-2 {:value 5}
:value-3 {:value 6}}
:Sold {:value false} } } ])
#'user/hmap
user> (declare print-json) ; forward declaration
#'user/print-json
user> (defn print-json-map [x]
(print "{ ")
(run! (fn [[k v]]
(print (str "\"" (name k) "\""))
(print ": ")
(print-json v))
x)
(println "}"))
#'user/print-json-map
user> (defn print-json-vec [x]
(print "[ ")
(run! #(print-json %) x)
(println "]"))
#'user/print-json-vec
user> (defn print-json-prim [x]
(cond (string? x) (print (str "\"" x "\""))
(number? x) (print x)
(boolean? x) (print x)
:else (println "CANNOT HAPPEN")))
#'user/print-json-prim
user> (defn print-json [x]
(cond (map? x) (print-json-map x)
(vector? x) (print-json-vec x)
:else (print-json-prim x)))
#'user/print-json
user> (print-json hmap)
[ { "Mineral-1": { "Mineral-name": { "value": "Gold"}
"Color": { "value": "Golden"}
"Weight": { "value": 2324.23}
"Count": { "value": 203}
"Price": { "value": 20320.49}
"DeliveryContainers": { "value-1": { "value": 1}
"value-2": { "value": 2}
"value-3": { "value": 3}
}
"Sold": { "value": true}
}
}
{ "Mineral-2": { "Mineral-name": { "value": "Silver"}
"Color": { "value": "Silvered"}
"Weight": { "value": 2313.23}
"Count": { "value": 425}
"Price": { "value": 12345.12}
"DeliveryContainers": { "value-1": { "value": 4}
"value-2": { "value": 5}
"value-3": { "value": 6}
}
"Sold": { "value": false}
}
}
]
nil
user>
Since this is for educational purpose I made a solution using more of Clojure's build in language features:
(import '[clojure.lang Sequential Keyword Symbol])
(import '[java.util Map])
(defprotocol MyJSON
(to-json [this]))
(def ^:dynamic *indent* 0)
(defmacro indent [& body]
`(binding [*indent* (inc *indent*)] ~#body))
(def indent-size 2)
(defn indent-space []
(apply str (repeat (* indent-size *indent*) " ")))
(defn comma-sep
([values] (comma-sep values ", "))
([values delim]
(apply str (butlast (interleave values (repeat delim))))))
(extend-protocol MyJSON
Boolean
(to-json [this] (str this))
String
(to-json [this] this)
Number
(to-json [this] (str this))
nil
(to-json [_] "null")
Keyword
(to-json [this] (name this))
Symbol
(to-json [this] (name this))
Sequential
(to-json [this] (str "[" (comma-sep (map to-json this)) "]"))
Map
(to-json [this] (str "\n" (indent-space) "{\n"
(indent (comma-sep
(map (fn [[k v]] (str (indent-space) (to-json k) ": " (to-json v))) this)
",\n"))
"\n"
(indent-space) "}" )))
Regarding {:value "1"}, which transforms "1", to and {:value-1 "1" :value-2 "2"}, which transforms to ["1" "2"]:
I suggest to do this transformation first and finally convert the transformed data to json.
Related
We have some data like this:
(def x {:title ["NAME" "CODE" "ORDER" "MIN" "MAX" "IMG"]
:show-in-list [true true true true false false]
:key [:name :code :order :min :max :image]
:input-type [:txt :txt :num :num :num :img]
:value [nil nil nil nil nil nil]
:required [true true false false false false]})
We want to filter these values by :required's bool value which results in:
{:title ["NAME" "CODE"],
:show-in-list [true true],
:key [:part_name :part_code],
:input-type [:txt :txt],
:value [nil nil],
:required [true true]}
You can filter each element with ':required' vector:
(let [x {:title ["NAME" "CODE" "ORDER" "MIN" "MAX" "IMG"]
:show-in-list [true false true true false false]
:key [:name :code :order :min :max :image]
:input-type [:txt :txt :num :num :num :img]
:value [nil nil nil nil nil nil]
:required [true true false false false false]}]
(->> x
(map (fn [[k v]]
;; value v (which is a vector) is filtered out by its :required filter
[k (->> (map #(if %1 %2 ::false) (:required x) v)
;; remove ::false element
(remove #(= ::false %)))]))
;; add the updated key-value pair
(into (empty x))))
returns...
{:title ("NAME" "CODE"),
:show-in-list (true false),
:key (:name :code),
:input-type (:txt :txt),
:value (nil nil),
:required (true true)}
i would advice slightly different approach:
since you already need to filter out data in this 'packed' data structure, i guess you would potentially need to perform some other operations on it, so won't it be better to unpack/decompose the data structure to separate maps?
it could look like this:
(defn decompose [data]
(let [ks (keys data)]
(apply map #(zipmap ks %&) (vals data))))
this one unpacks the data:
user> (decompose x)
({:title "NAME",
:show-in-list true,
:key :name,
:input-type :txt,
:value nil,
:required true}
{:title "CODE",
:show-in-list true,
:key :code,
:input-type :txt,
:value nil,
:required true}
;;....more maps
)
then the recompose:
(defn recompose [data]
(when (seq data)
(apply merge-with
conj
(zipmap (keys (first data)) (repeat []))
data)))
this one, in turn, packs back the unpacked data.
so now your task could be fulfilled by simply filtering decomposed data and recomposing it back:
(->> x
decompose
(filter :required)
recompose)
;;=> {:title ["NAME" "CODE"],
;; :show-in-list [true true],
;; :key [:name :code],
;; :input-type [:txt :txt],
;; :value [nil nil],
;; :required [true true]}
To me it looks more general, and (what is more important) more readable.
(defn bar [state [required value]]
(if required (conj state value) state))
(defn foo [required values]
(reduce bar [] (map vector required values)))
(zipmap (keys x) (map #(foo (:required x) %) (vals x)))
We want to filter these values by :required's bool value
Pair required and value, if required then add value to new collection
I have a collection that has the following structure [{:a 0} {:b 1} {:c 1} {:d 2} {:e 3} {:f 2}]. Basically, it is a tree, where an element of the vector is a node. What the number signify is the parent-child relationship. So, the element {:a 0} is the master parent (has no parents), while {:b 1}, {:c 1} are childs of {:a 0}. Also, {:d 2} is a child of {:c 1}.
What I want is to construct a list or a vector (doesn't matter at this point) that has the following structure:
[{:a {:b nil :c {:d {:e nil} :f nil}}}].
How can this be achieved?
This should work:
(fn [xs]
(loop [[x & rs :as xs] xs
m {}
lvl {}]
(if (seq xs)
(let [[k l] (first x)
p (conj (lvl (dec l) []) k)]
(recur
rs
(assoc-in m p nil)
(assoc lvl l p)))
m)))
As #jas mentioned we can't rely on key-order of map, so here we use lvl map to keep last seen element's path per level.
I tried to do Clojure but stuck with the nested hashmap.
I have a structure like this:
{:type "view"
children: [
{:type "view"
:id "123"}
{:type "view"
:children [
{:type "view"}]}]}
Now I want to add field :id to each hashmap with random string if not exist. To get something like this:
{:type "view"
:id "43434"
children: [
{:type "view"
:id "123"}
{:type "view"
:id "456"
:children [
{:type "view"
:id "5656"}]}]}
You can use clojure.walk/postwalk to do this:
(walk/postwalk
(fn [v]
(if (and (map? v) (nil? (:id v)))
(assoc v :id (str (rand-int 9999)))
v))
data)
=>
{:type "view"
:id "3086"
:children [{:type "view"
:id "123"}
{:type "view"
:id "8243"
:children [{:type "view" :id "3222"}]}]}
...where data is your input map. postwalk is traversing your nested maps, and associating an :id key (a random integer string) on every map that doesn't have one.
in addition to the walk variant there are some more:
the simplest solution would be the recursive update
(defn upd [{:keys [id children] :as data}]
(assoc data
:id (if id
id
(str (rand-int Integer/MAX_VALUE)))
:children (mapv upd children)))
#'user/upd
user> (upd data)
;;=> {:type "view",
;; :children [{:type "view", :id "123", :children []}
;; {:type "view",
;; :children [{:type "view", :id "35223257",
;; :children []}],
;; :id "551012526"}],
;; :id "1899442274"}
you can also use clojure's zippers for that:
(require '[clojure.zip :as z])
(loop [curr (z/zipper map? :children (fn [n ch] (assoc n :children (vec ch))) data)]
(if (z/end? curr)
(z/root curr)
(recur (z/next
(let [node (z/node curr)]
(if (:id node)
curr
(z/replace curr
(assoc node :id
(str "id-" (rand-int Integer/MAX_VALUE))))))))))
;;=> {:type "view",
;; :children [{:type "view", :id "123"}
;; {:type "view",
;; :children [{:type "view", :id "id-92807721"}],
;; :id "id-1357268462"}],
;; :id "id-803083206"}
Let's say I have a map :
{:top 2.8421709430404007E-14, :left 0, :down 240, :right 400N}
How can I iterate over it to convert everything into integers ?
{:top 0, :left 0, :down 240, :right 400}
An obvious solution would be :
{:top (:top m), :left (:left m), :down (:down m), :right (:right m)}
but it feels very repetitive.
I feel a reducing function could do nicely here, but I'm not sure how.
Doing a map over the entries could be another way:
(into {} (map (fn [[k v]] [k (int v)]) m))
;; {:top 0, :left 0, :down 240, :right 400}
I had to use reduce-kv:
(reduce-kv #(assoc %1 %2 (int %3)) {} {:top 2.8421709430404007E-14, :left 0, :down 240, :right 400N})
;;=>{:down 240, :left 0, :right 400, :top 0}
Edit : algo.generic.fmap looks like it would work as well.
Edit bis :
Thanks to #Andre for mentioning that a map-vals function exists in both weavejester/medley or prismatic/plumbing.
From medley :
(defn map-vals
"Maps a function over the values of an associative collection."
[f coll]
(reduce-map (fn [xf] (fn [m k v] (xf m k (f v)))) coll))
From plumbing :
(defn map-vals
"Build map k -> (f v) for [k v] in map, preserving the initial type"
[f m]
(cond
(sorted? m)
(reduce-kv (fn [out-m k v] (assoc out-m k (f v))) (sorted-map) m)
(map? m)
(persistent! (reduce-kv (fn [out-m k v] (assoc! out-m k (f v))) (transient {}) m))
:else
(for-map [[k v] m] k (f v))))
Input:
List of keys: [ :name :address :work]
Map 1: { :name "A" :address "A Street" }
Map 2: { :work "Work Ave" }
Output:
([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
This is what I have at the moment:
(defn maps-iterate [v & ms]
(map (fn [k] (into [] [k #(map (k %) ms)])) v))
(println (maps-iterate [ :name :address :work ] { :name "A" :address "A Street"} { :work "Work Ave" }))
Which gives me:
([:name #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#4b14b82b>]
[:address #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#3d47358f>]
[:work #<user$maps_iterate$fn__2$fn__3 user$maps_iterate$fn__2$fn__3#e0d5eb7>])
Try
(defn maps-iterate [v & ms]
(map (fn [k] (into [] [k (map #(k %) ms)])) v))
Or even better:
(defn maps-iterate [v & ms]
(map (fn [k] (cons k (map k ms))) v))
Note: if all keys are keywords, then you can use them as functions: (map k ms) instead of (map #(k %) ms). If they are not then you can't use them as functions. You need to write (map #(% k) ms)
How about this?
(for [k ks]
[k (map k [m1 m2])])
;;=> ([:name ("A" nil)] [:address ("A Street" nil)] [:work (nil "Work Ave")])
or, if you really want a flat vector in the results:
(for [k ks]
(apply vector k
(map k [m1 m2])))
;;=> ([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
user=> (def a { :name "A" :address "A Street" })
#'user/a
user=> (def b { :work "Work Ave" })
#'user/b
user=> (def c [ :name :address :work])
#'user/c
user=> (map #(vector %1 (%1 a) (%1 b)) c)
([:name "A" nil] [:address "A Street" nil] [:work nil "Work Ave"])
The following REPL example produces your output without the nil values indicated:
user> (def map1 { :name "A" :address "A Street" })
#'user/map1
user> (def map2 { :work "Work Ave" })
#'user/map2
user> (seq (merge map1 map2))
([:work "Work Ave"] [:name "A"] [:address "A Street"])