This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-04-18
Channels
- # beginners (25)
- # boot (30)
- # cljs-dev (22)
- # cljsjs (2)
- # cljsrn (1)
- # clojars (4)
- # clojure (223)
- # clojure-boston (1)
- # clojure-dusseldorf (1)
- # clojure-gamedev (8)
- # clojure-italy (5)
- # clojure-russia (122)
- # clojure-sg (3)
- # clojure-spec (26)
- # clojure-uk (42)
- # clojurescript (69)
- # clojuresque (10)
- # core-async (25)
- # cursive (10)
- # datascript (5)
- # datomic (12)
- # emacs (18)
- # garden (1)
- # interop (1)
- # jobs (1)
- # jobs-discuss (10)
- # leiningen (2)
- # liberator (1)
- # lumo (21)
- # nyc (2)
- # off-topic (210)
- # om (11)
- # om-next (3)
- # onyx (1)
- # pedestal (6)
- # re-frame (10)
- # rum (9)
- # specter (38)
- # uncomplicate (1)
- # vim (23)
- # yada (22)
I have a question, a while back I posted the following s.o. question: http://stackoverflow.com/questions/42551765/idiomatic-and-concise-way-of-working-with-nested-map-vector-structures-in-clojur and I have been playing for the past few hours with using spectre for this nested traversal problem. In essense I have some json data (maps and vectors) and I need to traverse to a certain depth, do a few operations, remember context, traverse a few more levels down, do a few operations etc
I am not really interested in changing the traverse structure. I assume I should be using collectors then?
@mbjarland so to clarify, you're not transforming the nested structure, you just want to perform some operations using values inside?
this is the groovy equivalent of what I want to do:
json.issues.each { issue ->
issue.changelog.histories.each { history ->
def date = DateTime.parse(history.created)
if (date < fromDate || date > toDate) return
def timeItems = history.items.findAll { it.field == 'timespent' }
if (!timeItems) return
def consultant = history.author.displayName
timeItems.each { item ->
def from = (item.from ?: 0) as Integer
def to = ( ?: 0) as Integer
timesheets[consultant].entries << new TimeEntry(date: date, issueKey: issue.key, secondsSpent: to - from)
}
}
}
it seems that the path traversals above json.issues.each
, issue.changelog.histories.each
, etc are a perfect match for specter
it sounds like you want to do a traverse
to get to the values you care about in the order you want
and then reduce
over that to generate your result
and multiple nested traverses to get hold of the different levels (issue, history, item) above?
use collect
/ collect-one
a simplified example:
(reduce conj [] (traverse [ALL (collect-one :name) :age] [{:name "Bob" :age 21} {:name "Alice" :age 20}]))
I've been trying various methods of getting the groovy code above expressive and terse in clojure, I think specter might be the answer
your path would look something like: [:issues ALL :changelog :histories ALL (selected? :date #(> % fromDate) #(< % toDate)) (collect-one :author :displayName) :items ALL #(= (:field %) "timespent") (collect-one :from (nil->val 0)) :to (nil->val 0)]
or something like that
this is by the way the json returned from a jira instance and we use the results for invoicing so it is an actual real world example
no problem
what if I need to do some computation at an intermediate level like parsing the date ?
a privilege to be able to get answers directly from the author and I have to say I'm impressed with the clojure community experience so far. Thanks again!
@mbjarland you can insert (view parse-date)
to parse before comparing
a function directly in a path is interpreted as a filter
ok got a last one, for context I have the following function:
(defn disect-json [json from-date to-date]
(let [path [:issues ALL
(collect-one :key)
:changelog :histories ALL
#(history-in-date-range? % from-date to-date)
(selected? [:items ALL #(= (:field %) "timespent")])
(collect-one (view #(parse-history-date %)))
(collect-one [:author :displayName])
:items ALL
(collect-one :from (nil->val "0") (view #(read-string %)))
(collect-one :to (nil->val "0") (view #(read-string %)))]]
(group-by #(nth % 2)
(reduce conj [] (traverse path json)))))
looking at the path expression, how do I terminate the path without selecting the currently navigated node and only keeping the collect-one
values?oh and the above works, so I’m happy as a clam after looking for months for a clean solution for this in clojure.
@mbjarland instead of collecting the last element, just navigate to it
terminate the path with :to (nil->val "0") (view #(read-string %)))
and drop the collect-one
also you can do (view parse-history-date)
to make it cleaner, same with usage of read-string
also if you just want a vector of results back, just do select
instead of (reduce ... (traverse ...))
finally, you should specify the path inline with the select
call so it can be properly optimized
or define the path
local using specter's path
macro
(let [json-path (path :issues ALL ...) ...