Skip to content

Instantly share code, notes, and snippets.

@ertugrulcetin
Last active May 28, 2025 13:12
Show Gist options
  • Save ertugrulcetin/d666022d5cedc7139bdf06965f9b6e61 to your computer and use it in GitHub Desktop.
Save ertugrulcetin/d666022d5cedc7139bdf06965f9b6e61 to your computer and use it in GitHub Desktop.
ClojureScript Patch: assoc, dissoc, update-in for JS Objects
;; ClojureScript Patch: assoc, dissoc, update-in for JS Objects
;; Extends assoc, dissoc, and update-in to work seamlessly with native JavaScript objects in ClojureScript.
;; This namespace bridges the gap between idiomatic ClojureScript data manipulation and JavaScript interop, enabling structural updates on #js objects using familiar Clojure semantics.
(ns cljs-engine.preload
(:refer-clojure :exclude [dissoc update-in assoc-in])
(:require
[applied-science.js-interop :as j]
[clojure.string :as str]))
(def kw->str
(memoize (fn [k]
(if (keyword? k)
(if (qualified-keyword? k)
(str (namespace k) "/" (name k))
(name k))
k))))
(def str->kw
(memoize (fn [str]
(if (string? str)
(if (str/includes? str "/")
(let [[f s] (str/split str #"/")]
(keyword f s))
(keyword str))
str))))
(defn- cljs? [o]
(or (instance? PersistentVector o)
(instance? PersistentArrayMap o)
(instance? PersistentHashMap o)
(instance? PersistentTreeMap o)
(instance? PersistentTreeSet o)
(instance? PersistentHashSet o)
(instance? PersistentQueue o)
(instance? PersistentArrayMapIterator o)
(instance? PersistentQueueIter o)
(instance? PersistentTreeMapSeq o)
(instance? PersistentArrayMapSeq o)))
(extend-type object
ILookup
(-lookup
([o k]
(j/get o (kw->str k)))
([o k not-found]
(let [v (j/get o (kw->str k))]
(if (nil? v) not-found v))))
ICollection
(-conj [coll o]
(doseq [[k v] (js/Object.entries o)]
(j/assoc! coll (kw->str k) v))
coll)
ISeqable
(-seq [o]
(when-let [keys (seq (js/Object.keys o))]
(map (fn [k]
[(str->kw k) (j/get o (kw->str k))])
keys)))
IMap
(-dissoc [coll k]
(js-delete coll (kw->str k))
coll)
IAssociative
(-contains-key? [coll k]
(j/call coll :hasOwnProperty (kw->str k)))
(-assoc [o k v]
(j/assoc! o (kw->str k) v))
ICounted
(-count [coll]
(j/get (js/Object.keys coll) :length)))
(extend-type function
ILookup
(-lookup
([o k]
(j/get o k))
([o k not-found]
(let [v (j/get o k)]
(if (nil? v) not-found v))))
IAssociative
(-contains-key? [coll k]
(j/call coll :hasOwnProperty (kw->str k)))
(-assoc [o k v]
(j/assoc! o (kw->str k) v)))
(extend-type array
ICollection
(-conj [coll o]
(j/call coll :push o))
ICounted
(-count [coll]
(j/get coll :length))
IAssociative
(-contains-key? [coll k]
(j/call coll :hasOwnProperty (kw->str k)))
(-assoc [o k v]
(j/assoc! o (kw->str k) v)))
(defn assoc-in [m [k & ks :as args] v]
(if (or (cljs? m)
(and (not (cljs? m))
(cljs? (get-in m (drop-last args)))))
(if ks
(assoc m k (assoc-in (get m k) ks v))
(assoc m k v))
(j/assoc-in! m (map kw->str args) v)))
(set! cljs.core/assoc-in assoc-in)
(defn dissoc
([coll] coll)
([coll k]
(when-not (nil? coll)
(-dissoc coll k)))
([coll k & ks]
(when-not (nil? coll)
(let [ret (dissoc coll k)]
(if ks
(recur ret (first ks) (next ks))
ret)))))
(set! cljs.core/dissoc dissoc)
(defn update-in
([m [k & ks] f]
(let [r (get m k)
r (if (and (nil? r) (not (cljs? m)) (> (count m) 0))
#js {}
r)]
(if ks
(assoc m k (update-in r ks f))
(assoc m k (f r)))))
([m [k & ks] f a]
(let [r (get m k)
r (if (and (nil? r) (not (cljs? m)) (> (count m) 0))
#js {}
r)]
(if ks
(assoc m k (update-in r ks f a))
(assoc m k (f r a)))))
([m [k & ks] f a b]
(let [r (get m k)
r (if (and (nil? r) (not (cljs? m)) (> (count m) 0))
#js {}
r)]
(if ks
(assoc m k (update-in r ks f a b))
(assoc m k (f r a b)))))
([m [k & ks] f a b c]
(let [r (get m k)
r (if (and (nil? r) (not (cljs? m)) (> (count m) 0))
#js {}
r)]
(if ks
(assoc m k (update-in r ks f a b c))
(assoc m k (f r a b c)))))
([m [k & ks] f a b c & args]
(let [r (get m k)
r (if (and (nil? r) (not (cljs? m)) (> (count m) 0))
#js {}
r)]
(if ks
(assoc m k (apply update-in r ks f a b c args))
(assoc m k (apply f r a b c args))))))
(set! cljs.core/update-in update-in)
(comment
(def data #js {})
(assoc data :a 1)
;;=> #js {:a 1}
(:a data)
;;=> 1
(update data :a inc)
;;=> #js {:a 2}
(assoc-in data [:c :d] 5)
(-> data :c :d)
;;=> 5
(assoc data :cljs-map {:b 1})
;;=> #js {:a 2 :cljs-map {:b 1}}
(update-in data [:cljs-map :b] inc)
;;=> #js {:a 2 :cljs-map {:b 2}}
(-> data :cljs-map :b)
;;=> 2
(merge #js {:a 1} #js {:b 2})
;;=> #js{:a 1, :b 2}
(seq #js {:a 1})
;;=> ([:a 1])
(dissoc #js {:a 1 :b 2} :a)
(def data-cljs {})
(assoc data-cljs :a 1)
;;=> {:a 1}
(update data-cljs :a inc)
;;=> {:a 1}
data-cljs
;;=> {}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment