ClojureScript

js->clj does not work on objects with null prototype

Details

  • Type: Defect Defect
  • Status: Open Open
  • Priority: Major Major
  • Resolution: Unresolved
  • Affects Version/s: 1.9.908
  • Fix Version/s: None
  • Component/s: None
  • Labels:
    None

Description

Version: 1.9.946
Follow up from issue CLJS-1998

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

cljs.user=> (js->clj (.create js/Object nil))
#object[Object]

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_rhino_repl/cljs/core.cljs:2930:10)
	 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)

Activity

Hide
Kurt Harriger added a comment -

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

Show
Kurt Harriger added a comment - 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
Mike Fikes made changes -
Field Original Value New Value
Description Version: 1.9.946
Follow up from issue CLJS-1998

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

cljs.user=> (js->clj (.create js/Object nil))
#object[Object]

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_rhino_repl/cljs/core.cljs:2930:10)
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)
Version: 1.9.946
Follow up from issue CLJS-1998

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

cljs.user=> (js->clj (.create js/Object nil))
#object[Object]

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_rhino_repl/cljs/core.cljs:2930:10)
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)
{code}
Mike Fikes made changes -
Affects Version/s 1.9.908 [ 11361 ]
Hide
Kurt Harriger added a comment -

```
(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."
([x] (js->clj x :keywordize-keys false))
([x & opts]
(let [{:keys [keywordize-keys]} opts
keyfn (if keywordize-keys keyword str)
f (fn thisfn [x]
(cond
(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 [k (js-keys x)]
[(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)

Show
Kurt Harriger added a comment - ``` (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." ([x] (js->clj x :keywordize-keys false)) ([x & opts] (let [{:keys [keywordize-keys]} opts keyfn (if keywordize-keys keyword str) f (fn thisfn [x] (cond (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 [k (js-keys x)] [(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)
Hide
Kurt Harriger added a comment -

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/a
cljs.user=> (aset a "test" 1)
1
cljs.user=> (extend-protocol IEncodeClojure
nil
(js>clj [x opts]
(let [{:keys [keywordize-keys]} opts
keyfn (if keywordize-keys keyword str)]
(into {} (for [k (js-keys x)]
[(keyfn k) (js->clj (unchecked-get x k)) opts])))))
nil
cljs.user=> (js->clj a)
#object[Object]
cljs.user=> (js-keys IEncodeClojure)
#js ["null"]
cljs.user=> (satisfies? IEncodeClojure a)
false
cljs.user=> (type a)
nil

```

Show
Kurt Harriger added a comment - 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/a cljs.user=> (aset a "test" 1) 1 cljs.user=> (extend-protocol IEncodeClojure nil (js>clj [x opts] (let [{:keys [keywordize-keys]} opts keyfn (if keywordize-keys keyword str)] (into {} (for [k (js-keys x)] [(keyfn k) (js->clj (unchecked-get x k)) opts]))))) nil cljs.user=> (js->clj a) #object[Object] cljs.user=> (js-keys IEncodeClojure) #js ["null"] cljs.user=> (satisfies? IEncodeClojure a) false cljs.user=> (type a) nil ```
David Nolen made changes -
Priority Major [ 3 ] Critical [ 2 ]
David Nolen made changes -
Priority Critical [ 2 ] Major [ 3 ]
Hide
Hendrik Poernama added a comment -

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.

Example:
(identical? "object" (goog/typeOf (.create js/Object nil #js {:a #js {:value 1}}))) => true

Show
Hendrik Poernama added a comment - 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. Example: (identical? "object" (goog/typeOf (.create js/Object nil #js {:a #js {:value 1}}))) => true

People

Vote (1)
Watch (3)

Dates

  • Created:
    Updated: