Welcome! Please see the About page for a little more info on how this works.

+3 votes
in ClojureScript by

Version: 1.9.946
Follow up from issue CLJS-1998

cljs.user=> (js->clj #js {})

cljs.user=> (js->clj (.create js/Object nil))


cljs.user=> (keys (js->clj (.create js/Object nil)))
org.mozilla.javascript.EcmaError: TypeError: Cannot find default value for object. (.cljs_rhino_repl/goog/../.cljs_rhino_repl/cljs/core.js#9915)

 cljs$core$seq (.cljs_rhino_repl/cljs/core.cljs:1212:13)
 cljs$core$keys (.cljs_rhino_repl/cljs/core.cljs:8648:3)
 (NO_SOURCE_FILE <cljs repl>:1:0)
 (NO_SOURCE_FILE <cljs repl>:1:0)


5 Answers

0 votes

Comment made by: kurtharriger

For more context the source of the object with null prototype is generated in a third party library I am using here https://github.com/ProseMirror/prosemirror-model/blob/cdded3d/src/schema.js#L13

0 votes

Comment made by: kurtharriger

(def a (.create js/Object null)) => #'cljs.user/a (type a) => nil

The implementation of js->clj explicitly checks for type js/Object.

(defn js->clj
"Recursively transforms JavaScript arrays into ClojureScript
vectors, and JavaScript objects into ClojureScript maps. With
option ':keywordize-keys true' will convert object fields from
strings to keywords."
((link: x) (js->clj x :keywordize-keys false))
((link: x & opts)

(let (link: {:keys [keywordize-keys)} opts
      keyfn (if keywordize-keys keyword str)
      f (fn thisfn (link: x)
            (satisfies? IEncodeClojure x)
            (-js->clj x (apply array-map opts))

            (seq? x)
            (doall (map thisfn x))

            (coll? x)
            (into (empty x) (map thisfn x))

            (array? x)
            (vec (map thisfn x))

            (identical? (type x) js/Object)
            (into {} (for (link: k (js-keys x))
                       (link: (keyfn k) (thisfn (unchecked-get x k)))))

            :else x))


I think perhaps this last test should be changed to (or (nil? (type a)) (identical? (type x) js/Object)) because the only way to create an object with a null prototype is explicitly and they do so because they are intended to be used as associative objects. (http://adripofjavascript.com/blog/drips/creating-objects-without-prototypes.html)

0 votes

Comment made by: kurtharriger

The above also suggests that maybe one could extend IEncodeClojure on nil?
a is not nil but its type is... so maybe extending protocol on nil should this work?

But it doesn't... maybe this is a bug in satisfies??

cljs.user=> (def a (.create js/Object nil))


cljs.user=> (aset a "test" 1)
cljs.user=> (extend-protocol IEncodeClojure
(-js->clj (link: x opts)

(let (link: {:keys [keywordize-keys)} opts
      keyfn (if keywordize-keys keyword str)]
  (into {} (for (link: k (js-keys x))
    (link: (keyfn k) (js->clj (unchecked-get x k)) opts))))))

cljs.user=> (js->clj a)

object(link: Object)

cljs.user=> (js-keys IEncodeClojure)

js (link: "null")

cljs.user=> (satisfies? IEncodeClojure a)
cljs.user=> (type a)


0 votes
_Comment made by: hpnm_

What about `(identical? (goog/typeOf x) "object")` instead of `(identical? (type x) js/Object)`?

Closure's typeOf correctly returns 'null' and 'array' and has some browser specific fixes.
Current test looks like a lot less code, so may need to performance test.

(identical? "object" (goog/typeOf (.create js/Object nil #js {:a #js {:value 1}}))) => true
0 votes
Reference: https://clojure.atlassian.net/browse/CLJS-2408 (reported by alex+import)