Skip to content

Instantly share code, notes, and snippets.

@piotr-yuxuan
Last active December 3, 2016 01:51
Show Gist options
  • Save piotr-yuxuan/a331950b300020a63a38a466fcd888d5 to your computer and use it in GitHub Desktop.
Save piotr-yuxuan/a331950b300020a63a38a466fcd888d5 to your computer and use it in GitHub Desktop.
Just an attempt to get rid of keys like :1, :2, :3, etc.
;; We have a problem because of json serialisation / deserialisation: in its
;; narrow way, when a map keyed with integers is sent over the network, the
;; deserialised result contains keys like :1, :234, which are integers
;; represented by keywords. There are good reasons to this but we also have
;; good reasons to find integers are more properly represented by numbers.
;;
;; There can be numerous ways to avoid this problem. The first one here is
;; very narrow and focus on our mere goal: box keys to integers.
(defn- deep-key-cast
"First argument precondition is a fonction applied on each possible key to
box. It returns nil if this key can't be boxed, otherwise a derived value
which can be easily parsed by boxing-fn (the second argument).
This auxiliary function is meant to improve code legibility."
{:forms '[((deep-key-cast precondition boxing-fn) data-structure)]}
[p b]
(fn [m]
(let [f (fn [[k v]] (if-let [u (p k)] [(b u) v] [k v]))]
;; only apply to maps
(clojure.walk/postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m))))
;; Other boxing are possible, to double (or to general numbers). However we're
;; specifically interested in parsing integers, which stands for primary keys.
(def deep-key-cast->int
"Take an data structure as single argument and try to box (nested) keys
to integer."
(let [precondition #(condp apply [%]
string? (re-matches #"^\d+$" %)
int? %
keyword? (->> % name (re-matches #"^\d+$"))
nil)
boxing-fn js/parseInt]
(deep-key-cast precondition boxing-fn)))
(println (deep-key-cast->int {:a [1 :2] :b "3" :4 5}))
; => {:a [1 :2] :b "3" 4 5}))
;; The second one is less terse but has much broader effects as it tries to box
;; any available data in the datastructure to integers. Here, general walk
;; algorithm remains valid but its implementation must be widened.
(defn eager-deep-cast
"First argument precondition is a fonction applied on each possible datum to
be boxed. It returns nil if this datum can't be boxed, otherwise a derived
value which can be easily parsed by boxing-fn (the second argument).
This auxiliary function is meant to improve code legibility."
{:forms '[((eager-deep-cast precondition boxing-fn) data-structure)]}
[precondition boxing-fn]
(fn recur-fn [datum]
(let [box-when-pred #(if-let [unboxed (precondition %)] (boxing-fn unboxed) %)]
(condp apply [datum]
map? (reduce (fn [a [k v]] (assoc a (box-when-pred k) (recur-fn v))) {} datum)
sequential? (->> datum (map recur-fn) ((condp apply [datum] vector? vec list? list)))
(box-when-pred datum)))))
;; Other boxing are possible, to double (or to general numbers). However we're
;; specifically interested in parsing integers, these standing for primary keys.
(def eager-deep-cast->int
"Take an data structure as single argument and try to box (nested) data
to integer."
(let [precondition #(condp apply [%]
string? (re-matches #"^\d+$" %)
int? %
keyword? (->> % name (re-matches #"^\d+$"))
nil)
boxing-fn js/parseInt]
(eager-deep-cast precondition boxing-fn)))
(println (eager-deep-cast->int {:a [1 :2] :b "3" :4 5}))
; => {:a [1 2] :b 3 4 5}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment