I am trying to find out how to access Javascript objects properties in ClojureScript. If I know in advance the name of the property, that is easy. To get foo.bar I just do
(.-bar foo)
Is there a way to access a property whose name is known only at runtime? I am looking for the equivalent of the JS syntax foo[dynamicBar]
You can use aget / aset to access properties known only at runtime.
;; Use clj->js to convert clj(s) map to javascript.
;; Note the #js {:bar 100} reader literal indicating a js map.
cljs.user> (def foo (clj->js {:bar 100}))
#js {:bar 100}
cljs.user> (.-bar foo)
100
cljs.user> (aget foo "bar")
100
cljs.user> (aset foo "baz" 200)
200
cljs.user> (.-baz foo)
200
Using string names may be also important in case when you want to take advantage of :optimizations :advanced compiler mode, but you don't have externs file covering your code.
See David Nolen's example using goog.object.get:
https://github.com/clojure/clojurescript/wiki/Dependencies#using-string-names
While aget works. This method was originally supposed to provide you access to array elements, not properties of js objects in general. goog.object's get method is a better way to communicate your intent.
Here are the implementations of both methods:
https://github.com/google/closure-library/blob/1b8a893271d790626b5cd652b922675c987f106d/closure/goog/object/object.js#L403
https://github.com/clojure/clojurescript/blob/d2d031605b1ad552077218c8f445868653c01744/src/main/clojure/cljs/core.cljc#L942
As you can see, (aget o key)generates javascript code o[key] directly, but goog.object.get calls a method which first checks if the key is present in o.
Related
In the clojurescript re-frame todomvc application we find the following snippet in the todomvc.views namespace.
(defn todo-list
[visible-todos]
[:ul.todo-list
(for [todo #visible-todos]
^{:key (:id todo)} [todo-item todo])])
Although I have read the Clojure chapter on metadata I don't quite understand the purpose of:
^{:key
in the snippet above. Please explain.
The :key is what React needs when you have many items, so that they can be unique within the group. But the latest version of React does not need these keys. So if you use the latest versions of reframe / Reagent, just try without the :key metadata.
This metadata is equivalent to placing :key within the component. So for example what you have is equivalent to:
[todo-item {:key (:id todo)} todo]
Using the metadata approach is a convenience, which must in some cases be easier than the 'first key in props passed to the component' approach.
Here's more explanation.
^{:key (:id todo)} [todo-item todo] would be equivalent to (with-meta [todo-item todo] {:key (:id todo)}), see https://clojuredocs.org/clojure.core/with-meta
Reagent uses this to generate the corresponding react component with a key. Keys help React identify which items have changed, are added, or are removed. here is the explanation: https://reactjs.org/docs/lists-and-keys.html
I am new to Clojure and Reagent. Kindly tell how to print the variable first inside the atom variable contacts?
(def app-state
(r/atom
{:contacts [{:first "Ben" :last "Lem" :middle "Ab"}]}))
First of all: the reagent tutorial is a really good place to start. It even gives you examples to solve exactly this problem.
Since reagents atom can be treated just as a regular Clojurescript atom, you can use all your normal sequence operations. Keep in mind that in order to access the current value, you have to dereference the atom via #.If you really just want to access the first :first in your atom:
(:first (first (:contacts #app-state))) or (get (first (get #app-state :contacts)) :first)
Or, if you think it's more readable
(-> #app-state
:contacts
first
:first)
I guess what you might want to do is define a few functions to make the access more easy such as:
(defn get-contacts!
"Returns the current vector of contacts stored in the app-state."
[]
(:contacts #app-state))
(defn get-first-names!
"Return a vector of all first names in the current list of contacts in the
app-state."
[]
(mapv :first (get-contacts!)))
Please keep in mind that in reagent (and in general really) you might want to dereference that atom as fiew times as possible, so look for a good place to dereference it and just use regular functions that operate on a simple sequence instead of an atom.
Still, I would really suggest you go read the aforementioned reagent tutorial.
Here is a concise way to access the value that you are looking for using Clojure's (get-in m ks) function:
(get-in #app-state [:contacts 0 :first])
Just as an extra, you may see this often written as
(->> #app-state
:contacts
(mapv :first)
first
and it's useful to understand what's going on here.
->> is a macro called thread-last which will re-write the code above to be
(first (mapv :first (:contacts #app-state)))
Thread last is a bit weird at first but it makes the code more readable when lots of things are going on. I suggest that on top of the reagent tutorial mentioned in the other comments, you read this.
#app-state will give you whatever is inside the r/atom and (:first (first (:contacts #app-state))) will return the first element and (println (:first (first (:contacts #app-state)))) will print output to the browser console (so you need to have the developer tools console open to see it).
Note that for println to output to the browser developer tools console you need to have this line in your code:
(enable-console-print!)
I have an app that logs Clojure maps to a log file via prn. A contrived entry from the log might be:
{:foo "bar"
:baz [:quux]
:body #<HttpInput org.eclipse.jetty.server.HttpInput#2a4bd173>}
I'd like to be able to paste this into a repl to inspect the "simple" keys and values, but the reader chokes on #< values.
CompilerException java.lang.RuntimeException:
Unable to resolve symbol: HttpInput in this context
I don't care about the #< values. Is there a way to get the reader to ignore them, or give me a chance to deal with them directly (sort of like *data-readers*)?
To the best of my knowledge, there's no way to make this input work with the reader without manually editing the log output. The default behavior for prn for unrecognized datatypes is to output something of the form:
#<(.getSimpleName (class o)) (str o)>
The # character indicates a reader macro, which dispatches on the following character, and the < character indicates an unreadable form; the reader will always throw an exception when it encounters this.
Your best bet might be to alter the logging for your application, to print something more readable. One way of doing this is providing an implementation of the print-method multimethod for your target class, e.g.:
(defmethod print-method org.eclipse.jetty.server.HttpInput [o w]
(print-method {:type "HttpInput"
:request-uri (-> o ._connection .getRequest .getUri str)}
w))
(This might not work for the protected field _connection, but hopefully you get the picture.)
I tried to follow the documentation for clojure.instant/read-instant-timestamp, which reads:
clojure.instant/read-instant-timestamp
To read an instant as a java.sql.Timestamp, bind *data-readers* to a
map with this var as the value for the 'inst key. Timestamp preserves
fractional seconds with nanosecond precision. The timezone offset will
be used to convert into UTC.`
The following result was unexpected:
(do
(require '[clojure.edn :as edn])
(require '[clojure.instant :refer [read-instant-timestamp]])
(let [instant "#inst \"1970-01-01T00:00:09.999-00:00\""
reader-map {'inst #'read-instant-timestamp}]
;; This binding is not appearing to do anything.
(binding [*data-readers* reader-map]
;; prints java.util.Date -- unexpected
(->> instant edn/read-string class println)
;; prints java.sql.Timestamp -- as desired
(->> instant (edn/read-string {:readers reader-map}) class println))))
How can I use the *data-readers* binding? Clojure version 1.5.1.
clojure.edn functions by default only use data readers stored in clojure.core/default-data-readers which, as of Clojure 1.5.1, provides readers for instant and UUID literals. If you want to use custom readers, you can do that by passing in a :readers option; in particular, you can pass in *data-readers*. This is documented in the docstring for clojure.edn/read (the docstring for clojure.edn/read-string refers to that for read).
Here are some examples:
(require '[clojure.edn :as edn])
;; instant literals work out of the box:
(edn/read-string "#inst \"2013-06-08T01:00:00Z\"")
;= #inst "2013-06-08T01:00:00.000-00:00"
;; custom literals can be passed in in the opts map:
(edn/read-string {:readers {'foo identity}} "#foo :asdf")
;= :asdf
;; use current binding of *data-readers*
(edn/read-string {:readers *data-readers*} "...")
(The following section added in response to comments made by Richard Möhn in this GitHub issue's comment thread. The immediate question there is whether it is appropriate for a reader function to call eval on the data passed in. I am not affiliated with the project in question; please see the ticket for details, as well as Richard's comments on the present answer.)
It is worth adding that *data-readers* is implicitly populated from any data_readers.{clj,cljc} files that Clojure finds at the root of the classpath at startup time. This can be convenient (it allows one to use custom tagged literals in Clojure source code and at the REPL), but it does mean that new data readers may appear in there with a change to a single dependency. Using an explicitly constructed reader map with clojure.edn is a simple way to avoid surprises (which could be particularly nasty when dealing with untrusted input).
(Note that the implicit loading process does not result in any code being loaded immediately, or even when a tag mentioned in *data-readers* is first encountered; the process which populates *data-readers* creates empty namespaces with unbound Vars as placeholders, and to actually use those readers one still has to require the relevant namespaces in user code.)
The *data-readers* dynamic var seems to apply to the read-string and read functions from clojure.core only.
(require '[clojure.instant :refer [read-instant-timestamp]])
(let [instant "#inst \"1970-01-01T00:00:09.999-00:00\""
reader-map {'inst #'read-instant-timestamp}]
;; This will read a java.util.Date
(->> instant read-string class println)
;; This will read a java.sql.Timestamp
(binding [*data-readers* reader-map]
(->> instant read-string class println)))
Browsing through the source code for clojure.edn reader here, I couldn't find anything that would indicate that the same *data-readers* var is used at all there.
clojure.core's functions read and read-string use LispReader (which uses the value from *data-readers*), while the functions from clojure.edn use the EdnReader.
This edn library is relatively new in Clojure so that might be the reason why the documentation string is not specific enough regarding edn vs. core reader, which can cause this kind of confusion.
Hope it helps.
So I think clojure.core/bean is pretty close to what I want, but I'm working with a Java application that has nested beans, such that I end up with maps like this:
{:month-total 3835.0 :name "Jan's Meat Diner" :owners #<BarOwner[] [Lcom.fancypants.BarOwner;#1fb332d}
How, do I call bean recursively on a Java object so that I can get my imaginary BarOwner object to emit itself as a map, too:
{:month-total 3835.0 :name "Jan's Meat Diner" :owners { [:name "Jack"] [:name "Jill"] } }
Edit 1
I have found that clojure/java.data and from-java is probably a better fit for this kind of thing than bean.
Although it's probably not an ideal answer to "how to use bean recursively", using more of the richer contrib libraries under the Clojure community's site did solve it. Specifically
clojure/java.data
provides simple recursive bean resolving, and can be configured to handle java types specifically in the hairy cases. I'd recommend this to other people who want to use bean.
It is very tricky to find out what is a bean and what is not. This seems to do the trick for beans inside beans and properties that are lists. Probably you will want to add more classes to the probably-bean? function and perhaps some support for properties that are maps.
(defn probably-bean? [o]
(and (not (coll? o))
((complement #{Class Long String clojure.lang.Keyword}) (class o))))
(defn transf [o]
(cond
(instance? java.util.List o) (into [] (map transf o))
(probably-bean? o) (into {} (seq (bean o)))
:else o))
(defn to-bean [o]
(clojure.walk/prewalk #(transf %) o))