mfikes / cljs-bean Goto Github PK
View Code? Open in Web Editor NEWEfficient JavaScript object interop via idiomatic ClojureScript
License: Eclipse Public License 1.0
Efficient JavaScript object interop via idiomatic ClojureScript
License: Eclipse Public License 1.0
E.g. (bean x :prototypes true)
Some code is from ClojureScript and test.check and can be separated out into a separate directory.
A license section in the readme can indicate this.
There are several places where a value is unwrapped if it is a bean or ArrayVector. These could be factored out into simpler logic.
I've tried using ->clj
with mouse events in a Reagent app with both (fn [e] (->clj e) ...)
where the fn is the handler for the event, but it doesn't convert (it remains JS object). Using ->clj
on the event's .-nativeEvent
doesn't work either - what should I do ?
ci-reduce
is private
Especially in self-hosted, inlining -lookup calls speeds things up anywhere from 1.25 to 4.0
In the app I'm working on we use custom converters to be able to preserve namespaced keywords through a cljs->js->cljs roundtrip. In cljs-bean this fits perfectly, we were able to reuse our code by plugging it in as prop->key
and key->prop
converters.
That's for the keys, which is obviously the most common and sensible use case. But we also occasionally need to do the same for property values. So for us it would be pretty great to be able to extend the cond
statement in the ->val
function.
cljs.user=> (require '[cljs-bean.core :refer [bean]])
nil
cljs.user=> (def x (bean #js {:a 1}))
#'cljs.user/x
cljs.user=> (count x)
1
cljs.user=> (count (dissoc x :b))
0
Hi, thanks in advance for your help and this library!
I've got a question about the following code. Is this expected? If I were to change ->clj
to (js->clj % :keywordize-keys true)
the code would pass. My understanding was that ->clj
should behave similarly to js->clj
.
Cheers!
{:deps {cljs-bean/cljs-bean {:mvn/version "1.7.0"}}
(deftest cljs-bean-test
(let [clj-obj (->clj #js {:coll #js [#js {:id "foo"}]})]
(is (= (conj (:coll clj-obj) {:id "bar"})
[{:id "foo"} {:id "bar"}]))))
FAIL in (cljs-bean-test)
expected: (= (conj (:coll clj-obj) {:id "bar"}) [{:id "foo"} {:id "bar"}])
actual: (not (= [#js {:id "foo"} {:id "bar"}] [{:id "foo"} {:id "bar"}]))
This shows how two maps that are {:a 1, :b {:x 2}}
can turn into two different JavaScript representations. The problem is that assoc
on a bean can assoc a non-primitive value without it snapshotting to a regular persistent data structure.
cljs.user=> (require '[cljs-bean.core :refer [->clj ->js]])
nil
cljs.user=> (->js {:a 1 :b {:x 2}})
#js {:a 1, :b #js {:x 2}}
cljs.user=> (assoc (->clj #js {:a 1}) :b {:x 2})
{:a 1, :b {:x 2}}
cljs.user=> (->js *1)
#js {:a 1, :b {:x 2}}
cljs.user=> (require '[cljs-bean.core :refer [bean]])
nil
cljs.user=> (seq (bean #js {}))
()
The Bean
type implements IAssociative
-contains-key?
by simply calling contains?
With CLJS-3283, contains?
is being revised to first check if a type implements IAssociative
and then directly calls -contains-key?
This causes infinite recursion with Bean
implementation.
When I try to interact with the objects returned by the Google Cloud getProjects
function I get different results when using ->clj
and bean :recursive true
(p/let [[ps] (.getProjects resource #js {:filter "name:uc*"})]
(->> ps
(map #(bean % :recursive true)
first
cljs.pprint/pprint)))
;; =>
{:metadata
{:projectNumber "XXXXXXXX",
:projectId "XXXXX",
:lifecycleState "ACTIVE",
:name "XXXXX",
:createTime "2019-11-28T09:56:03.185Z",
:parent {:type "folder", :id "1004516592721"}},
:baseUrl "/projects",
:parent #object[Resource [object Object]],
:id "XXXXX",
:createMethod #object[bound wrapper],
:methods
{:create true,
:delete true,
:exists true,
:get true,
:getMetadata true,
:setMetadata {:reqOpts {:method "PUT"}}},
:interceptors [],
:Promise #object[Promise],
:restore #object[wrapper],
:create #object[wrapper],
:delete #object[wrapper],
:exists #object[wrapper],
:get #object[wrapper],
:getMetadata #object[wrapper],
:setMetadata #object[wrapper],
:request_ #object[Function],
:request #object[wrapper],
:requestStream #object[Function]}
vs.
(p/let [[ps] (.getProjects resource #js {:filter "name:uc*"})]
(->> ps
(map ->clj)
first
cljs.pprint/pprint)))
;; =>
#object[Project [object Object]]
Is this because the objects returned from the Google Cloud library is not "pure" javascript Objects?
I would love to add something to the README about this gotcha, but not sure how to formulate it as I don't really understand the underlying reason for this happening.
set-empty-colls!
is created using defn-
but also has ^:private
For now, for the following code:
(def e (js/Error. "test string"))
;; 1
(bean/e)
;; 2
(->clj/e)
{:clojure$core$protocols$Datafiable$ #js {}, :clojure$core$protocols$Datafiable$datafy$arity$1 #object[Function]
#object[Error Error: test string]
Would be better if it can give things like
{"message":foo, "line":foo, "column":foo, "stack":foo}
Add support for IIterable
/ -iterator
.
Be careful with the project name, because Pinto in Brazil is the same of Dick. When someone search for Pinto project on the google, some unrequested pictures can be appear.
cljs.user=> (def o (bean #js {:a 1}))
#'cljs.user/o
cljs.user=> (defrecord Lu [fqn])
cljs.user/Lu
cljs.user=> (get o (->Lu "a"))
1
Bean has :key->prop and :prop->key,
But ->js and ->clj lack such control.
In the Object Extraction doc (https://github.com/mfikes/cljs-bean/blob/master/doc/object.md) it gives an example where assoc
ing a string key causes a snapshot.
The doc should be updated to describe that assoc
ing a map value also causes a snapshot. The doc can describe how associng a bean can be used as a workaround to avoid snapshotting.
Internally cache the result of js-keys if they are produced. This can help performance because js-keys is an O(n) operation. Also, caching helps ensure that seqs, etc. are produced in a consistent order.
Allow a bean to be in a mode where it is recursive. This means it behaves akin to js->clj where object values are represented as beans, and array values are converted to vectors, and each of these container's values are recursively treated in the same way.
Illustrative example:
cljs.user=> obj
#js {:a 1,
:m #js {:b 2, :m #js {:c 3}},
:v #js [1
2
#js {:x 3,
:m #js {:a 1,
:v #js [1 2]}}]}
cljs.user=> (bean obj)
{:a 1,
:m #js {:b 2, :m #js {:c 3}},
:v #js [1
2
#js {:x 3,
:m #js {:a 1, :v #js [1 2]}}]}
cljs.user=> (js->clj obj :keywordize-keys true)
{:a 1,
:m {:b 2, :m {:c 3}},
:v [1 2 {:x 3, :m {:a 1, :v [1 2]}}]}
cljs.user=> (bean obj :recursive true)
{:a 1,
:m {:b 2, :m {:c 3}},
:v [1 2 {:x 3, :m {:a 1, :v [1 2]}}]}
(t/write (t/writer :json) (bean/->clj #js {:hello "world"}))
throws
writer.js:464 Uncaught Error: Cannot write
at Object.writer.maybeQuoted (writer.js:464)
at Object.writer.marshalTop (writer.js:471)
at Transit$Writer.writer.Writer.write (writer.js:501)
at cognitect$transit$write (transit.cljs:278)
at transit.cljs:8
Not sure what is happening there, but happy to dig in and put up a PR.
For my use case, I want to provide a user of my library the ability to immutably update a JS object using the core library and then I unwrap the object so that I can pass it to React.
Something akin to this:
(defn Component [props]
(let [cljs-props (bean props)
cljs-props' (assoc cljs-props :foo "bar")]
(react/createElement (unwrap cljs-props') "baz")))
AFAICT updates to the bean return a regular map so, for my use case, it just might not fit. What do you think, @mfikes ?
CLJS Bean values are immutable. This lets identical?
work on them along with other guarantees you gain via immutability. (Reinforce that passed JavaScript maps and arrays should be effectively immutable to ensure this.)
But the object are not persistent. (Operations like assoc
involve CoW.)
It is worth documenting (perhaps in the Object Extraction section?) some of these aspects.
Code in this project is derived from code in ClojureScript.
If you compare perf for assoc
on beans vs persistent maps, you can see that persistent maps have around a speedup of 6:
cljs.user=> (require '[cljs-bean.core :refer [->clj]])
nil
cljs.user=> (simple-benchmark [m (->clj #js {:a 1, :b 2})] (assoc m :c 3) 1e6)
[m (->clj #object[cljs.tagged-literals.JSValue])], (assoc m :c 3), 1000000 runs, 678 msecs
nil
cljs.user=> (simple-benchmark [m {:a 1, :b 2}] (assoc m :c 3) 1e6)
[m {:a 1, :b 2}], (assoc m :c 3), 1000000 runs, 112 msecs
If we add fast paths for primitives in the code that deals with snapshot checks and unwrapping, we can get bring the speedup down to a factor of 2.
If we type hint, then, if externs inference is enabled in the compiler, then something like this would work under advanced:
(let [b (bean #js {:myInc (fn [x] (inc x))})]
(.myInc (object b) 1))
This works fine:
(t/write (t/writer :json) {"some-test?" true}))
This fails:
(t/write (t/writer :json) (cljs-bean/->clj #js {"some-test?" true}))
Error: Cannot write
at Object.maybeQuoted (compiled/cljs-runtime/com/cognitect/transit/impl/writer.js:469:23)
at Object.marshalTop (compiled/cljs-runtime/com/cognitect/transit/impl/writer.js:476:57)
at Transit$Writer.w [as write] (compiled/cljs-runtime/com/cognitect/transit/impl/writer.js:506:26)
at Object.cognitect$transit$write [as write] (compiled/cljs-runtime/cognitect/transit.cljs:285:11)
at f (...)
at compiled/cljs-runtime/promesa/impl.cljc:86:30
at processTicksAndRejections (internal/process/task_queues.js:93:5)โ
Interestingly wrapping the bean in (take)
also prevents the error:
(t/write (t/writer :json) (take 1 (cljs-bean/->clj #js {"some-test?" true})))
Is this expected?
I'm processing a data structure that is an object with a list of results containing maps of properties describing a row in a data table.
The names of the properties may contain spaces so using cljs-bean's bean
function with a custom key fn to process it, lowercase the keys, and replace any spaces with dashes (-
).
Test Data:
[{:id 1,
:properties
{:uid
{:type "formula", :formula {:type "string", :string "abcdefg"}}}}
{:id 2,
:properties
{:uid
{:type "formula", :formula {:type "string", :string "hijklmn"}}}}
{:id 3,
:properties
{:uid
{:type "formula", :formula {:type "string", :string "opqrstu"}}}}]
Test:
(map #(get-in % [:properties :uid :formula :string]) beaned)
Expected:
(abcdefg hijklmn opqrstu)
Actual:
(nil nil nil)
To make it easier to reproduce, here's a replit with this exact problem recreated with the above test data.
https://replit.com/@eccentric-j/nbb-cljs-bean-oddity-repro#src/cli/core.cljs
Just run it to show the data, the expected, and the actual data which should match the above. It will then enter a repl from which the data may be inspected, or you may fork it to directly edit the files.
I don't really understand why it prints the way I would expect, but processing it shows a different result.
If you run:
(map #(get-in % [:properties :UID :formula :string]) beaned)
That does work, returning the expected list value but it seems to be ignoring the call to bean with a specific prop->key
fn to lower-case the keys. It does not match what gets printed.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.