clojureringcompojurecompojure-api

Clojure nested json response


I am new to clojure and I am trying to make a simple API with 3 endpoints. I am trying to implement an endpoint which will take each row of a query and put it to a json. So I have an SQLite database. These are my entries for example:

{:timestamp 2020-09-11 14:29:30, :lat 36.0, :long 36.0, :user michav}
{:timestamp 2020-09-11 14:31:47, :lat 36.0, :long 36.0, :user michav}

So I want a json response like the below:

{:get
    :status 200
    :body {:timestamp "2020-09-11 14:29:30" 
           :lat 36.0 
           :long 36.0
           :user "michav"}
          {:timestamp "2020-09-11 14:31:47" 
           :lat 36.0 
           :long 36.0
           :user "michav"}
   }

} Here is my code that I am trying to fix it in order to get the above result.

(def db
    {:classname   "org.sqlite.JDBC"
     :subprotocol "sqlite"
     :subname     "db/database.db"
    }) 
 
 (defn getparameter [req pname] (get (:params req) pname))

 (defn output
    "execute query and return lazy sequence"
    []
    (query db ["select * from traffic_users"]))

 (defn print-result-set
    "prints the result set in tabular form"
    [result-set]
    (doseq [row result-set]
        (println row)))

  (defn request-example [req]
    (response {:get {:status 200 
                     :body  (-> (doseq [row output]
                                {:timestamp  (getparameter row :timestamp) :lat  (getparameter row :lat) :long  (getparameter row :long) :user  (getparameter row :user)} ))
                     }}))
  
(defroutes my_routes
    (GET "/request" [] request-example)
    (route/resources "/"))

(def app (-> #'my_routes wrap-cookies wrap-keyword-params wrap-params wrap-json-response))

The error I encounter is the following :

java.lang.IllegalArgumentException: Don't know how to create ISeq from: mybank.core$output
                             RT.java:557 clojure.lang.RT.seqFrom
                             RT.java:537 clojure.lang.RT.seq
                            core.clj:137 clojure.core/seq
                            core.clj:137 clojure.core/seq
                             core.clj:65 mybank.core/request-example[fn]
                             core.clj:65 mybank.core/request-example
                             core.clj:63 mybank.core/request-example
                         response.clj:47 compojure.response/eval1399[fn]
                          response.clj:7 compojure.response/eval1321[fn]
                            core.clj:158 compojure.core/wrap-response[fn]
                            core.clj:128 compojure.core/wrap-route-middleware[fn]
                            core.clj:137 compojure.core/wrap-route-info[fn]
                            core.clj:146 compojure.core/wrap-route-matches[fn]
                            core.clj:185 compojure.core/routing[fn]
                           core.clj:2701 clojure.core/some
                           core.clj:2692 clojure.core/some
                            core.clj:185 compojure.core/routing
                            core.clj:182 compojure.core/routing
                         RestFn.java:139 clojure.lang.RestFn.applyTo
                            core.clj:667 clojure.core/apply
                            core.clj:660 clojure.core/apply
                            core.clj:192 compojure.core/routes[fn]
                            Var.java:384 clojure.lang.Var.invoke
                         cookies.clj:171 ring.middleware.cookies/wrap-cookies[fn]
                   keyword_params.clj:32 ring.middleware.keyword-params/wrap-keyword-params[fn]
                           params.clj:57 ring.middleware.params/wrap-params[fn]
                             json.clj:42 ring.middleware.json/wrap-json-response[fn]
                            Var.java:384 clojure.lang.Var.invoke
                           reload.clj:18 ring.middleware.reload/wrap-reload[fn]
                       stacktrace.clj:17 ring.middleware.stacktrace/wrap-stacktrace-log[fn]
                       stacktrace.clj:80 ring.middleware.stacktrace/wrap-stacktrace-web[fn]
                            jetty.clj:27 ring.adapter.jetty/proxy-handler[fn]
                        (Unknown Source) ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle
                 HandlerWrapper.java:127 org.eclipse.jetty.server.handler.HandlerWrapper.handle
                         Server.java:500 org.eclipse.jetty.server.Server.handle
                    HttpChannel.java:386 org.eclipse.jetty.server.HttpChannel.lambda$handle$1
                    HttpChannel.java:562 org.eclipse.jetty.server.HttpChannel.dispatch
                    HttpChannel.java:378 org.eclipse.jetty.server.HttpChannel.handle
                 HttpConnection.java:270 org.eclipse.jetty.server.HttpConnection.onFillable
             AbstractConnection.java:311 org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded
                   FillInterest.java:103 org.eclipse.jetty.io.FillInterest.fillable
                ChannelEndPoint.java:117 org.eclipse.jetty.io.ChannelEndPoint$2.run
                 EatWhatYouKill.java:336 org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask
                 EatWhatYouKill.java:313 org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce
                 EatWhatYouKill.java:171 org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce
                 EatWhatYouKill.java:135 org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce
               QueuedThreadPool.java:806 org.eclipse.jetty.util.thread.QueuedThreadPool.runJob
               QueuedThreadPool.java:938 org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run
                        (Unknown Source) java.lang.Thread.run

Solution

  • Your mistake is on this line:

    (doseq [row output] ...
    

    The error message

    Don't know how to create ISeq from: mybank.core$output
    

    gives the clue. The variable output is the function, not a sequence like doseq expects. You meant to call the output function, so you need to use parentheses to create a function call like:

    (doseq [row (output)] ...
    

    Update

    You need to include an external library to convert between EDN data and a JSON string. My favorite way is my own library Tupelo Clojure. Use it like this:

    (ns tst.demo.core
      (:use tupelo.core tupelo.test))
    
    (let [data [{:timestamp "2020-09-11 14:29:30", :lat 36.0, :long 36.0, :user "michav"}
                {:timestamp "2020-09-11 14:31:47", :lat 36.0, :long 36.0, :user "michav"}]]
      (println (edn->json data)))
    

    with result:

    [{"timestamp":"2020-09-11 14:29:30","lat":36.0,"long":36.0,"user":"michav"},
     {"timestamp":"2020-09-11 14:31:47","lat":36.0,"long":36.0,"user":"michav"}]
    

    You will need a line like this in your project.clj:

    [tupelo "20.08.27"] 
    

    Please also see this template project for an easy way to get started. Enjoy!