With the following app:
; src/webapp/core.clj
(ns webapp.core
(:require [compojure.core :refer [defroutes GET]]
[ring.middleware.json :as mid-json]
[clj-time.jdbc]))
(defn foo [request]
{:body {:now (org.joda.time.DateTime/now)}})
(defroutes routes
(GET "/foo" [] foo))
(def app
(-> routes
(mid-json/wrap-json-response)))
Hitting the /foo endpoint gives me this error:
com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class org.joda.time.DateTime: 2017-10-21T03:38:16.207Z
Is there a simple way to get ring-json to encode the DateTime object? Do I have to write my own middleware to convert it to e.g. a string first? If so, how would I do that? (I've never written ring middleware before).
My project.clj has these dependencies FYI:
[[org.clojure/clojure "1.8.0"]
[org.clojure/java.jdbc "0.6.1"]
[ring/ring-jetty-adapter "1.4.0"]
[compojure "1.4.0"]
[ring/ring-json "0.4.0"]
[clj-time "0.14.0"]]
If you're using Cheshire to generate JSON, you can extend its protocol to handle serialization then it should "just work":
(extend-protocol cheshire.generate/JSONable
org.joda.time.DateTime
(to-json [dt gen]
(cheshire.generate/write-string gen (str dt))))
Additionally, the following modification made it work for projects that are using the time library tick.alpha.api. I was getting the error
#error {:cause Cannot JSON encode object of class:
class java.time.Instant: 2019-10-23T00:31:40.668Z
:via
[{:type com.fasterxml.jackson.core.JsonGenerationException
:message Cannot JSON encode object of class:
class java.time.Instant: 2019-10-23T00:31:40.668Z
:at [cheshire.generate$generate invokeStatic generate.clj 152]}]
Implementing the following in the file db/core.clj fixed the issue for me.
(extend-protocol cheshire.generate/JSONable
java.time.Instant
(to-json [dt gen]
(cheshire.generate/write-string gen (str dt))))
Related
Im pretty clear on how to parse JSON from http requests. But I have a JSON file locally I would like to use within my code.
I have tried to find a solution on google but I am struggling to figure out how to read a local JSON file from the file system
Thanks
Vinn
Use clojure/data.json library:
Add this dependency into project.clj:
[org.clojure/data.json "2.4.0"]
Add this requirement into namespace definition:
(:require [clojure.data.json :as json])
Then use read-str with slurp. I made example file filename.json with this content:
{"name":"John", "age":30, "car":null}
and read it like this:
(json/read-str (slurp "filename.json"))
=> {"name" "John", "age" 30, "car" nil}
Well, what's the difference between a json arriving from an http request and a json arriving from a local file? I suppose the real question then is "how to read from a local file", no?
Here is how to read a json from a string using clojure/data.json:
(def json-str (json/read-str "{\"a\":1,\"b\":{\"c\":\"d\"}}"))
Now, lets put the same string into a file
echo '{"a":1,"b":{"c":"d"}}' > /tmp/a.json
And lets read it from the file:
(def from-file (slurp "/tmp/a.json"))
(def json-file (json/read-str from-file))
Make sure they are the same:
(when (= json-str json-file)
(println "same" json-file))
Which would print "same" and the parsed json value.
I am trying to figure out how I would upload a Json object to S3 using clojure through lambda. I am not doing any json type checking and I am just trying to write a handler such that as soon as receives a json object, it uploads that object to the bucket. Here is my code:
(ns clojurehandler.s3_clojure_handler
(:gen-class
:name "clojurehandler.S3ClojureHandler"
:implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler])
(:require [clojure.data.json :as json]
[clojure.string :as s]
[clojure.java.io :as io]
[aws.sdk.s3 :as s3]))
(def cred {:access-key "..", :secret-key ".."})
(defn handle-event [event]
(s3/put-object cred
"bucket-name" "name" event
:content-type "application/json"))
(defn -handleRequest [this is os context]
(handle-event (io/reader is)))
I am using this library: https://github.com/weavejester/clj-aws-s3
which I know has been deprecated but it seems like it should do what I am aiming to do.
I have already set the role of my lambda function to enable putting objects in S3 buckets, but I get a task timed out after 3 seconds error when I try to test it. What am I doing wrong?
Thanks
I have my dependencies
(ns test.core
(:require [reagent.core :as reagent :refer [atom]]
[ajax.core :refer [GET]]))
I then have my handler that handles the responses to my ajax call
(defn handle-response [response]
(println (type response))
(println film))
THe data from my call is JSON, in the browser it looks like this:
{"id":3,"name":"Sicario","star":"Emily Blunt","rating":5}
When I run the above Clojure, I see this:
cljs.core/PersistentArrayMap core.cljs:192:23
{id 3, name Sicario, star Emily Blunt, rating 5}
Is their a way for me to go from the PersistArrayMap, and destructure it into id, name, star and rating?
I tried
(let [film (js->clj (.parse js/JSON response) :keywordize-keys true)]
...)
Hoping I would get a map named film, but no avail!
I think maybe I could use (get-in), but can't get the syntax right for this either.
Thanks,
Got it,
I was missing :response-format :json and :keywords? true from my GET call
Now, it looks like this:
(GET (str "https://localhost:5001/api/films/" film-name)
{:response-format :json
:keywords? true
And it all works.
My project parses JSONs, with a read/write library, called:
cheshire.core
I was having problems, trying to get the decode (func) to work, so I started messing around with:
data.json
My JSON contains data that consists of a field named "zone" this contains a vector with :keys inside, like so {:zone : [:hand :table]} that is stored into strings within the vector stored like so: {"zone" : ["hand" "table"]}
So I figured out how to convert the sample data using:
(mapv keyword {"zone" : ["hand"]})
which was great, I then needed to figure out how to implement a decoder for cheshire, I couldn't do this with my logic, I only spent like an hour working on this, but I had been using data.json, and the decoder function is relatively easy I think.
I got my project to work, here is some sample code:
(ns clojure-noob.core (:require
[cheshire.core :refer [decode]]
[clojure.data.json :as j-data]
) (:gen-class))
(defn -main
"I don't do a whole lot ... yet."
[& args]
)
this is using cheshire:
(let [init (decode "{\"zone\" : [\"hand\"]}" true
(fn [field-name]
(if (= field-name "zone")
(mapv keyword [])
[])))]
(println (str init)))
this is using data.json:
(defn my-value-reader [key value]
(if (= key :zone)
(mapv keyword value)
value))
(let [init (j-data/read-str
"{\"zone\" : [\"hand\"]}"
:value-fn my-value-reader
:key-fn keyword)]
(println (str init)))
I want the bottom result of these two from the console:
{:zone ["hand"]}
{:zone [:hand]}
The problem is I would like to do this using cheshire 😎
p.s. I am reading the factory section of cheshire? maybe this easier?
I would agree with #TaylorWood. Don't mess with the decoder, just do a bite in a time. First, parse json. Second, transform the result.
(def data "{\"zone\" : [\"hand\"]}")
(-> data
(cheshire.core/decode true)
(update-in ["zone"] (partial mapv keyword)))
#=> {:zone [:hand]}
I recommend you use a tool like schema.tools to coerce the input. You can add a second pass that attempts to coerce JSON strings into richer clojure types.
Here's some sample code!
;; require all the dependencies. See links below for libraries you need to add
(require '[cheshire.core :as json])
(require '[schema.core :as s])
(require '[schema.coerce :as sc])
(require '[schema-tools.core :as st])
;; your data (as before)
(def data "{\"zone\" : [\"hand\"]}")
;; a schema that wants an array of keywords
(s/defschema MyData {:zone [s/Keyword]})
;; use `select-schema` along with a JSON coercion matcher
(-> data
(json/decode true)
(st/select-schema MyData sc/json-coercion-matcher))
;; output: {:zone [:hand]}
Using defschema to define the shape of data you want gives you a general solution for serializing into JSON while getting the full benefit of Clojure's value types. Instead of explicitly "doing" the work of transforming, your schema describes the expected outcome, and hopefully coercions can do the right thing!
Links to libraries:
- https://github.com/plumatic/schema
- https://github.com/metosin/schema-tools#coercion
Note: you can do a similar thing with clojure.spec using metosin/spec-tools. Check out their readme for some help.
When defining a compojure handler e.g. by using the defroutes macro, I can do something like this:
(defroutes home-routes
(GET "/myhome/:id" [ id ] (home-page)))
(defn home-page [ id ]
( ... do something ... ))
So I know how to pass a piece of the path parameter. But imagine I want to return a HAL+JSON object with a selflink. How would I get defroutes to pass the whole URI to the home-page function?
The Ring request map contains all the necessary information to construct a "selflink". Specifically, :scheme, :server-name, :server-port, and :uri values can be assembled into full request URL. When I faced this problem I created Ring middleware that adds the assembled request URL to the Ring request map. I could then use the request URL in my handlers as long as I pass the request map (or some subset of it) into the handler. The following snippet shows one way of implementing this:
(defroutes app-routes
(GET "/myhome/:id" [id :as {:keys [self-link]}] (home-page id self-link))
(route/resources "/")
(route/not-found "Not Found"))
(defn wrap-request-add-self-link [handler]
(fn add-self-link [{:keys [scheme server-name server-port uri] :as r}]
(let [link (str (name scheme) "://" server-name ":" server-port uri)]
(handler (assoc r :self-link link)))))
(def app
(-> app-routes
handler/site
wrap-request-add-self-link))