Map literal must contain an even number of forms - clojure

I'm trying to make an requiest to a web service which requires data in json and a secret (:key)
(ns fdsfdsfds.core
(:require [clj-http.client :as client])
(:require [clojure.data.json :as json]))
(defn -main [& args]
(client/post "https://fsdfdsfd.com/api/fdsfds"
{:body {(json/write-str {:key "fdsfdsfdsfd"})}}))
I'm having an error:
Exception in thread "main" java.lang.RuntimeException: Map literal must contain an even number of forms
There're the even number of them, though.

The problem is here:
{:body { (json/write-str {:key "fdsfdsfdsfd"}) }}
^-- single item missing value? --^
^----- this is a map too
There's nothing paired with the function call.
The :body has a map as a value, but you only have a function in there, not a possible key for its value, or if that is the key, there's no value for it.
You probably want to remove the outer map brackets and leave:
{:body (json/write-str {:key "fdsfdsfdsfd"})}
EDIT AFTER COMMENTS:
You're asking why the example on the site is using a map. Look carefully at the value being used, it's a string
(client/post "url://site.com/api"
{:basic-auth ["user" "pass"]
:body "{\"json\": \"input\"}"
;; ...
The map is made up of lines of key/value pairs. The first is
key = :basic-auth, value = ["user" "pass"]
The value here is an array.
The second line is:
key = :body, value = "any old string"
In this case the string is an escaped map, the same that would be returned from calling json/write-str

Related

Can't extract key from Map

I'm hitting an API that returns some json that I am trying to parse. I look a the keys of the returned parsed values and it says that it has a key "id", but when I try to get the id from the map, I get nothing back.
E.g.:
(require '[clj-http.client :as client]
'[cheshire.core :refer :all])
(def app-url "http://myapp.com/api/endpoint")
(defn get-application-ids [] (parse-string (:body (client/get app-url))))
(defn -main [] (println (map :id (get-application-ids))))
This returns (nil nil nil). Which, AFAIKT, it shouldn't - instead it should return the value of the :id field, which is not null.
Helpful facts:
When I run (map keys (get-application-ids)) to find the keys of the returned structure, I get ((id name attempts) (id name attempts) (id name attempts)).
(type (get-application-ids)) returns clojure.lang.LazySeq
(map type (get-application-ids)) returns (clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap clojure.lang.PersistentArrayMap)
(println (get-application-ids)) returns (one example of the three returned):
({id application_1595586907236_1211, name myname, attempts [{appSparkVersion 2.4.0-cdh6.1.0, lastUpdated 2020-07-26T20:18:47.088GMT, completed true, lastUpdatedEpoch 1595794727088, sparkUser super, endTimeEpoch 1595794726804, startTime 2020-07-26T20:04:05.998GMT, attemptId 1, duration 880806, endTime 2020-07-26T20:18:46.804GMT, startTimeEpoch 1595793845998}]})
Everything about this tells me that (map :id (get-application-ids)) should return the value of the id field, but it doesn't. What am I missing?
It seems you are using cheshire.core/parse-string. This will return string keys, not keywords. See this example.
So, it appears that your key is the string "id", not the keyword :id. To verify this theory, try putting in the debugging statement:
(prn (:body (client/get app-url)))
To ask Cheshire to convert map keys from strings to keywords, use the form
(parse-string <json-src> true) ; `true` => output keyword keys
See also this list of documentation sources. Especially study the Clojure CheatSheet daily.

Reagent atom value is stil nil after reset function

I made service endpoint api for getting single object by id and it works as expected. I tested it with Postman and in handler function. I use cljs-ajax library for asynchronous client. I cant change the state of Reagent atom when I get response. Here is the code:
(ns businesspartners.core
(:require [reagent.core :as r]
[ajax.core :refer [GET POST]]
[clojure.string :as string]))
(def business-partner (r/atom nil))
(defn get-partner-by-id [id]
(GET "/api/get-partner-by-id"
{:headers {"Accept" "application/transit+json"}
:params {:id id}
:handler #(reset! business-partner (:business-partner %))}))
When I tried to access business-partner atom I got nil value for that atom. I can't figure out why because another method is almost the same except it get's list of business partners and works fine.
When I change the get-partner-by-id function:
(defn get-partner-by-id [id]
(GET "/api/get-partner-by-id"
{:headers {"Accept" "application/transit+json"}
:params {:id id}
:handler (fn [arg]
(println :handler-arg arg)
(reset! business-partner (:business-partner arg))
(println "Business partner from handler: " #business-partner))}))
Output in the browser console:
:handler-arg {:_id 5e7ad2c84b5c2d44583e8ecd,
:address Main Street,
:email nenmit#gmail.com,
:phone 555888,
:name Nen Mit}
Business partner from handler: nil
So, as you can see, I have my object in handler as desired, but when I try to reset my atom nothing happens. That's the core of the problem I think. Thank you Alan.
When in doubt, use debug print statements. Make your handler look like this:
:handler (fn [arg]
(println :handler-arg arg)
(reset! business-partner (:business-partner arg)))
You may also want to use clojure.pprint/pprint to pretty-print the output, or also add (type arg) to the output.
You may also want to initialize the atom to a specific value like
:bp-default so you can see if the nil you observe is the original one or if it is being reset to nil.
Update
So it is clear the key :business-partner does not exist in the map you are receiveing. This is what you must debug.
Trying to pull a non-existent key out of a map always returns nil. You could also use the 3-arg version of get to make this explicit. Convert
(:business-partner arg) => (get arg :business-partner ::not-found)
and you'll see the keyword ::not-found appear in your atom, verifying what is occurring.
In order to catch these problems early, I nearly always use a simple function grab from the Tupelo library like so:
(:business-partner arg) => (grab :business-partner arg)
The grab function will throw an exception if the expected key is not found. This provides early-warning of problems so you can track them down faster.
Another hint: next time use prn instead of println and it will retain double-quotes on string output like:
"Main Street"

Using split on each element of a vector

Basically, I have used slurp to get the contents of a file that is supposed to be a database. I've split the data already once and have a vector that contains all the information correctly. Now I would like to split each element in the vector again. This would give me a vector of vectors. My problem is I can't seem to find the right way to iterate through the vector and make my changes. The changes either don't work or are not stored in the vector.
Using doseq:
(doseq [x tempVector]
(clojure.string/split x #"|")
)
If I add a print statement in the loop it prints everything spaced out with no changes.
What am I doing wrong?
The str/split function returns a new vector of strings, which you need to save. Right now it is being generated and then discarded. You need something like this:
(ns xyz
(:require
[clojure.string :as str]))
(def x "hello there to you")
(def y (str/split x #" ")) ; save result in `y`
(def z (str/split x #"e")) ; save result in `z`
y => ["hello" "there" "to" "you"]
z => ["h" "llo th" "r" " to you"]
You can read clojure basics online here: https://www.braveclojure.com .
I recommend buying the book as it has more stuff than the online version.
If you have several strings in a vector, you can use the map function to split each of them in turn:
(def my-strings
["hello is there anybody in there?"
"just nod if you can hear me"
"is there anyone at home?"])
(def my-strings-split
(mapv #(str/split % #" ") my-strings))
my-strings-split =>
[["hello" "is" "there" "anybody" "in" "there?"]
["just" "nod" "if" "you" "can" "hear" "me"]
["is" "there" "anyone" "at" "home?"]]
To restructure your slurped lines of text into a collection of vectors of words you could do something like:
(use '[clojure.string :as str :only [split]])
(defn file-as-words [filename re]
(let [lines (line-seq (clojure.java.io/reader filename))
line-words (vec (mapv #(str/split %1 re) lines))]
line-words))
Here we define a function which first uses line-seq to slurp the file in and break it into a collection of lines, then we map an anonymous function which invokes clojure.string/split on each line in the initial collection, breaking each line up into a collection of words delimited by the passed-in regular expression. The collection of vectors-of-words is returned.
For example, let's say we have a file named /usr/data/test.dat which contains
Alice,Eating,001
Kitty,Football,006
May,Football,004
If we invoke file-as-words by using
(file-as-words "/usr/data/test.dat" #",")
you get back
[["Alice" "Eating" "001"] ["Kitty" "Football" "006"] ["May" "Football" "004"]]

Clojure kebab case on selected keywords

I want to change certain key's in a large map in clojure.
These key's can be present at any level in the map but will always be within a required-key
I was looking at using camel-snake-kebab library but need it to change only a given set of keys in the required-key map. It doesn't matter if the change is made in json or the map
(def my-map {:allow_kebab_or-snake {:required-key {:must_be_kebab ""}}
:allow_kebab_or-snake2 {:optional-key {:required-key {:must_be_kebab ""}}}})
currently using /walk/postwalk-replace but fear it may change keys not nested within the :required-key map
(walk/postwalk-replace {:must_be_kebab :must-be-kebab} my-map))
ummmm.. could you clarify: do you want to change the keys of the map?! or their associated values?
off-topic: your map above is not correct (having two identical keys :allow_kebab_or_snake - i-m assuming you're just underlining the point and not showing the actual example :))
postwalk-replace WILL replace any occurrence of the key with the value.
so if you know the exact map struct you could first select your sub-struct with get-in and then use postwalk-replace :
(walk/postwalk-replace {:must_be_kebab :mus-be-kebab}
(get-in my-map [:allow_kebab_or_snake :required-key]))
But then you'll have to assoc this into your initial map.
You should also consider the walk function and construct your own particular algorithm if the interleaved DS is too complex.
Here is a solution. Since you need to control when the conversion does/doesn't occur, you can't just use postwalk. You need to implement your own recursion and change the context from non-convert -> convert when your condition is found.
(ns tst.clj.core
(:use clj.core clojure.test tupelo.test)
(:require
[clojure.string :as str]
[clojure.pprint :refer [pprint]]
[tupelo.core :as t]
[tupelo.string :as ts]
))
(t/refer-tupelo)
(t/print-versions)
(def my-map
{:allow_kebab_or-snake {:required-key {:must_be_kebab ""}}
:allow_kebab_or-snake2 {:optional-key {:required-key {:must_be_kebab ""}}}})
(defn children->kabob? [kw]
(= kw :required-key))
(defn proc-child-maps
[ctx map-arg]
(apply t/glue
(for [curr-key (keys map-arg)]
(let [curr-val (grab curr-key map-arg)
new-ctx (if (children->kabob? curr-key)
(assoc ctx :snake->kabob true)
ctx)
out-key (if (grab :snake->kabob ctx)
(ts/kw-snake->kabob curr-key)
curr-key)
out-val (if (map? curr-val)
(proc-child-maps new-ctx curr-val)
curr-val)]
{out-key out-val}))))
(defn nested-keys->snake
[arg]
(let [ctx {:snake->kabob false}]
(if (map? arg)
(proc-child-maps ctx arg)
arg)))
The final result is shown in the unit test:
(is= (nested-keys->snake my-map)
{:allow_kebab_or-snake
{:required-key
{:must-be-kebab ""}},
:allow_kebab_or-snake2
{:optional-key
{:required-key
{:must-be-kebab ""}}}} ))
For this solution I used some of the convenience functions in the Tupelo library.
Just a left of field suggestion which may or may not work. This is a problem that can come up when dealing with SQL databases because the '-' is seen as a reserved word and cannot be used in identifiers. However, it is common to use '-' in keywords when using clojure. Many abstraction layers used when working with SQL in clojure take maps as arguments/bindings for prepared statements etc.
Ideally, what is needed is another layer of abstraction which converts between kebab and snake case as needed depending on the direction you are going i.e. to sql or from sql. The advantage of this aproach is your not walking through maps making conversions - you do the conversion 'on the fly" when it is needed.
Have a look at https://pupeno.com/2015/10/23/automatically-converting-case-between-sql-and-clojure/

key delete! invoked when the method is PUT

I'm trying to build a simple rest api, using liberator and monger :
(ns tm.place.endpoint
(:require [liberator.core :refer [defresource]]
[monger [collection :as mc] [query :as mq]]
[clojure.data [json :as json]]
monger.json))
(defresource entity-place-resource [id]
:available-media-types ["application/json"]
:allowed-methods [:get :put :delete]
:exists? (if-let [place (mc/find-map-by-id "place" id)] {:place place})
:handle-ok :place
; :handle-created (fn [ctx]
; (let [body (-> ctx :request :body)]
; (assoc body :_id id)))
:handle-not-found []
:handle-not-acceptable "Should accept application/json"
:handle-method-not-allowed (json/write-str
{:message "Can only handle get, put and delete"})
; :handle-not-implemented
; :handle-no-content
:put! (fn [ctx]
(let [body (-> ctx :request :body)]
(mc/update-by-id "place" id (assoc body :_id id))))
:delete! (mc/remove-by-id "place" id)
:can-put-to-missing? false)
I'm using advanced rest client to see if it works. If the method is :get or :delete, it does perfectly what I want (It first check if a document is exists, and then do the appropriate action). However, if the method is :put, it simply spits out http 201 created, which I think the request is success, but the corresponding document is deleted, not updated.
If I comment out the :delete! line, the :put! is work as expected, so I'm guessing the culprit is the :delete! line, but I have no idea why because since I'm using :put method, I'm assuming :delete! should remain untouched. Any idea why?
As you have defined your delete! as a bare function it's going to get evaluated when you don't expect. The pattern you have followed for put! needs to be applied, where you return a fn that does the work rather than act directly.
http://clojure-liberator.github.io/liberator/faq.html
Due to the exact details of the defresource macro expansion, the form used as the value is evaluated at unexpected times.