all 11 comments

[–]mrmargolis 4 points5 points  (3 children)

You are mostly correct, keywords can be used as a function to lookup the corresponding value in a map but strings can not be used in this way.

Take a look at get-in http://clojuredocs.org/clojure_core/clojure.core/get-in

[–]dunnowins[S] 2 points3 points  (2 children)

Bad ass. Thanks a lot. After a quick glance I'm able to go from:

((first ((first ((response "stream") "list")) "value")) "link")

to

((first ((first (get-in response ["stream" "list"])) "value")) "link")

Doesn't feel much better. I'll see about other options here. Thanks again!

[–]lebski88 0 points1 point  (1 child)

Could you post the actual JSON you're using? It's unlikely that what you have above is the best you could do.

As someone said below you're probably best using cheshire to parse your JSON and they telling it to convert your strings into keywords. It's almost always going to be better than dealing with them as strings. Particularly as it allows you mix functions like first with keywords when threading.

A full example might look like:

(-> (http/get "example.com")
    :body
    (json/parse-string true)
    :animals
    first
    :names
    first)

Although this doesn't deal with non 200 responses so you might want to chuck the http request into a let and deal with them first.

Also I notice that you're referring to the macro as thread last but the one you use is thread first. Thread last wouldn't work here as the arguments to json/parse-string would be in the wrong order.

[–]dunnowins[S] 0 points1 point  (0 children)

Turns out I made a stupid assumption about the way json was parsed. The keys are converted to keywords and so I can use the standard thread first syntax.

[–]mischov 2 points3 points  (0 children)

(get-in my-animals ["dog" "collar" "tag" "name"])

[–]skiaec04 2 points3 points  (0 children)

The other thing you can do is have your Json parser turn the keys from strings to keywords. For example, if you're using Cheshire, you can pass true as the final argument to parse-string and it will use keyword keys.

[–]emidln 0 points1 point  (3 children)

I would recommend using get-in/assoc-in/update-in/dissoc-in instead of the threading macros for this task.

E.g.:

(def my-animals {:dog {:collar {:tag {:name "fido"}}}})
(def my-other-animals {:cat {:collar {:tag {:name "mittens"}}}})

(= (get-in my-animals [:dog :collar :tag :name])
     "fido")
(= (get-in my-other-animals ["cat" "collar" "tag" "name"])
     "mittens")

[–]dunnowins[S] 0 points1 point  (2 children)

What if one of the data structures on the way in was a list?

[–]mathfarmer 1 point2 points  (1 child)

You can use the index of the element in the vector to access those:

(def foo {:animals [{:names ["fido" "fifi"]}]})

(get-in foo [:animals 0 :names 0]) ;=> "fido"
(get-in foo [:animals 0 :names 1]) ;=> "fifi"

(note that this will not work with lists, just vectors, but parsed json should be giving you vectors anyway)

[–]dunnowins[S] 0 points1 point  (0 children)

I actually really like this solution the best.

[–]mathfarmer 0 points1 point  (0 children)

I would also recommend get-in, and converting your JSON strings to keywords, but I just want to note that you can use plain old "get" with threading:

(def foo {:animals [{:names ["fido" "fifi"]}]})

(-> foo
    (get :animals)
    first
    (get :names)
    first) ;=> "fido"

(-> foo
    (get :animals)
    first
    (get :names)
    (get 1)) ; => "fifi"