Last active
December 3, 2016 01:51
-
-
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.
This file contains hidden or 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
;; 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